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.