Skip to content

Internationalization

formulon-cell ships with en and ja dictionaries. Locale is changed at runtime — labels update in place without remounting. Hosts can extend a bundled dictionary or register new ones.

Glossary: i18n controller

The runtime controller exposed on SpreadsheetInstance.i18n. It owns the active locale, the dictionary registry, and notifies the store on changes so the chrome can re-render.

Swap locale at runtime

ts
const instance = await Spreadsheet.mount(host, { workbook, locale: 'en' })

instance.i18n.setLocale('ja')

The localeChange event fires after the swap, so host status bars or persistence layers can sync.

Override entries without forking

i18n.extend(locale, partialDictionary) merges your overrides on top of the existing dictionary:

ts
instance.i18n.extend('ja', {
  contextMenu: {
    copy: 'コピーする'
  }
})

The override is scoped to the running instance; the package's built-in dictionary stays untouched.

Declarative alternative: the strings prop

The React and Vue adapters accept a strings prop on <Spreadsheet> (MountOptions.strings in the vanilla package). It is applied right after mount and is equivalent to calling i18n.extend(locale, strings) yourself — use it when you'd rather declare overrides alongside the other mount props than call the controller imperatively.

Register a new locale

ts
import fr from './locales/fr.js'

instance.i18n.register('fr', fr)
instance.i18n.setLocale('fr')

Locale objects are plain JavaScript values shaped like the bundled dictionaries. A trimmed example:

ts
export default {
  contextMenu: {
    copy: 'Copier',
    paste: 'Coller',
    pasteSpecial: 'Collage spécial…',
    cut: 'Couper'
  },
  toolbar: {
    bold: 'Gras',
    italic: 'Italique',
    underline: 'Souligner'
  },
  // ...more sections
}

Hosts can ship dictionaries as static JSON, async-load them, or generate them from a translation pipeline.

Reading the dictionary

Host chrome and extensions can read the active dictionary through the controller's strings property — there is no t(key) accessor:

ts
const label = instance.i18n.strings.contextMenu.copy

Built-in chrome reads labels the same way, straight off the strings tree. strings always reflects the currently active locale (plus any extend() overrides); missing keys in a registered locale fall back to the bundled English dictionary so the UI never shows raw keys.

Locale and engine compatibility profile are separate

Engine locale behavior (date parsing, currency, list separators) is driven by the Excel compatibility profile, not the UI's i18n.setLocale. Switching the UI to French does not change formula evaluation results. See Locale profiles for the engine side.

Localizing function metadata

The dictionaries above cover chrome labels, not the function catalog behind autocomplete and the formula tooltip — those come from the engine's structural function catalog. To localize function signatures, descriptions, and display names, install a provider on the workbook:

ts
wb.setFunctionMetadataProvider({
  SUM: {
    aliases: { 'ja-JP': '合計' },
    localized: {
      'ja-JP': {
        signature: 'SUM(数値1, [数値2], …)',
        description: '範囲内の数値を合計します。'
      }
    }
  }
})

Keys are canonical UPPERCASE function names. Pass null to clear the provider. Each field falls back with precedence locale-override → entry-default → engine-value, so you supply only what you want to change and leave arity and the rest to the engine. Provider metadata is display-only — it never affects formula parsing or evaluation.

For merging a catalog outside a live workbook, the package also exports the pure helper mergeFunctionMetadata alongside LOCALE_TAGS and localeTag.

Persisting the user's choice

Subscribe to localeChange and persist the locale yourself — the package does not write to localStorage:

ts
instance.on('localeChange', ({ locale }) => {
  localStorage.setItem('cell.locale', locale)
})

Restore on next mount:

ts
const saved = localStorage.getItem('cell.locale') ?? 'en'
const instance = await Spreadsheet.mount(host, { workbook, locale: saved })