Rettangoli FE Docs
Internationalization

Rettangoli FE treats internationalization as a framework feature. Apps define locale files, FE turns them into lazy-loadable runtime assets, and components read strings through i18n without passing i18n dependencies through setup.js.

Configuration

Add fe.i18n to rettangoli.config.yaml:

fe:
  dirs:
    - "src/components"
    - "src/pages"
  setup: "src/setup.web.js"
  outfile: "_site/public/main.js"
  i18n:
    dir: "src/i18n"
    defaultLocale: "en"
    fallbackLocale: "en"
    locales:
      - "en"
      - "vi"
      - "ja"

Locale Files

Use one YAML file per locale:

src/i18n/
  en.yaml
  vi.yaml
  ja.yaml

Each file uses namespace -> key -> content.

common:
  saveButton: "Save"
  cancelButton: "Cancel"

projectsPage:
  title: "Projects"
  emptyState: "No projects yet"

commandLineConditional:
  branchesLabel: "Branches"

The locale is the filename, so en.yaml contains English strings and vi.yaml contains Vietnamese strings.

Key Hierarchy

Always use exactly two levels inside each locale file.

Valid:

common:
  saveButton: "Save"

Invalid:

saveButton: "Save"

Invalid:

common:
  actions:
    save: "Save"

Recommended namespaces:

Namespace Use for
common Generic UI labels such as Save, Cancel, Loading
domain Shared product vocabulary
Component name Component-owned copy, such as commandLineConditional
Page name Page-owned copy, such as projectsPage
rtglUi Optional app overrides for Rettangoli UI copy

View Usage

Use i18n.namespace.key directly in .view.yaml. It is available in template content, template bindings, and event listener payloads.

template:
  - rtgl-text s=h1: ${i18n.projectsPage.title}
  - rtgl-button: ${i18n.common.saveButton}

Single-level keys are not supported:

- rtgl-text s=h1: ${i18n.branchesLabel}

Three-level keys are not supported:

- rtgl-text s=h1: ${i18n.common.actions.save}

Dynamic Text

i18n function calls are not part of the view contract.

Do not write:

- rtgl-button: ${i18n.completedCount({ count: completedCount })}

Do not write internal helper calls:

- rtgl-button: ${__rtglI18n("completedCount", completedCount)}

Compute dynamic text in JavaScript before it reaches the view:

export const selectViewData = ({ state, i18n }) => ({
  completedLabel: `${state.completedCount} ${i18n.todos.completedSuffix}`,
});
template:
  - rtgl-button: ${completedLabel}

Listener payloads can include translated strings:

refs:
  saveButton:
    eventListeners:
      click:
        handler: handleSave
        payload:
          label: ${i18n.common.saveButton}

Changing Locale

FE injects a locale service into handlers and store context.

locale.current();      // current locale
locale.available();    // configured locales
await locale.load("vi");
await locale.set("vi");

Use locale.set() from a normal handler:

refs:
  languageSelect:
    eventListeners:
      change:
        handler: handleChangeLocale
        payload:
          nextLocale: ${_event.target.value}

template:
  - select#languageSelect:
      - option value=en: "English"
      - option value=vi: "Vietnamese"
      - option value=ja: "Japanese"
export const handleChangeLocale = async ({ locale }, { nextLocale }) => {
  await locale.set(nextLocale);
};

locale.set() lazy-loads the locale JSON if needed, caches it, and re-renders mounted FE components.

FE starts from defaultLocale and does not persist locale selection. Apps that remember or derive a user's preferred locale own that state and can call locale.set(nextLocale) from app logic. Page-level metadata such as <html lang> is app-owned too.

Build Output

FE generates JSON assets next to the configured bundle.

With:

fe:
  outfile: "_site/public/main.js"
  i18n:
    dir: "src/i18n"
    locales: ["en", "vi"]

Build output includes:

_site/public/main.js
_site/public/i18n/en.json
_site/public/i18n/vi.json

The runtime resolves locale JSON URLs relative to the generated FE bundle, so apps do not need to configure a separate public path.

Development Hot Reload

When rtgl fe watch is running, editing an existing configured locale YAML file updates the development catalogs through HMR. Mounted FE components re-render with the new content without replacing the locale runtime, so the current locale and existing subscriptions are preserved.

Adding or removing a locale source file performs a full page reload. Changes to the configured locale list still require updating rettangoli.config.yaml and restarting the watcher.