-
Notifications
You must be signed in to change notification settings - Fork 0
Localization
Justin Willis edited this page Feb 3, 2026
·
1 revision
Coho uses lit-localize in runtime mode for internationalization. This allows locale switching without page reloads and lazy-loads translation files on demand.
import { msg } from '@lit/localize';
// Simple string
html`<span>${msg('Hello World')}</span>`;
// String with interpolation (use str tag)
import { msg, str } from '@lit/localize';
html`<span>${msg(str`${count} followers`)}</span>`;Add the @localized() decorator before @customElement():
import { msg, localized } from '@lit/localize';
@localized()
@customElement('my-component')
export class MyComponent extends LitElement {
render() {
return html`<p>${msg('Translatable text')}</p>`;
}
}src/
├── config/localization.ts # Configuration and locale switching
├── generated/
│ ├── locale-codes.ts # Auto-generated locale constants
│ └── locales/
│ └── es.ts # Spanish translations (auto-generated structure)
xliff/
└── es.xlf # XLIFF file for translators
lit-localize.json # lit-localize configuration
tsconfig.localize.json # TypeScript config for lit-localize tools
-
Source strings are wrapped with
msg()in the source code - Extract command scans code and generates XLIFF files for translation
- Build command generates locale modules from translated XLIFF
-
Runtime lazy-loads locale modules when
setLocale()is called -
Components with
@localized()automatically re-render when locale changes
# Extract strings from source code to XLIFF files
npm run localize:extract
# Build locale modules from translated XLIFF files
npm run localize:buildFollow this process to add new strings or update existing translations:
-
Add Strings in Code: Wrap user-facing text with
msg()in your components. -
Extract: Run
npm run localize:extract. This updatesxliff/*.xlffiles with new strings. -
Translate: Open the
.xlffiles and add translations for the new<trans-unit>elements.- Tip: Use an XLIFF editor or a text editor to modify the
<target>tags.
- Tip: Use an XLIFF editor or a text editor to modify the
-
Build: Run
npm run localize:build. This generates the TypeScript files insrc/generated/locales/. - Verify: Start the app and switch locales to verify the new translations.
- Commit: Commit the changes to source code, XLIFF files, and generated locale files.
// For simple strings
import { msg } from '@lit/localize';
// For strings with interpolation
import { msg, str } from '@lit/localize';import { msg, localized } from '@lit/localize';
@localized()
@customElement('my-component')
export class MyComponent extends LitElement {// Before
html`<button>Save</button>`;
// After
html`<button>${msg('Save')}</button>`;
// With interpolation
html`<span>${msg(str`${count} items`)}</span>`;
// With HTML (use html tag inside msg)
html`${msg(html`Click <b>here</b> to continue`)}`;npx lit-localize extract
# Edit xliff/es.xlf with translations
npx lit-localize build{
"targetLocales": ["es", "fr"] // Add new locale code
}Edit src/config/localization.ts:
const localeModules: Record<string, () => Promise<any>> = {
es: () => import('../generated/locales/es.js'),
fr: () => import('../generated/locales/fr.js'), // Add new import
};npx lit-localize extract # Creates xliff/fr.xlf
# Translate the XLIFF file
npx lit-localize build # Generates src/generated/locales/fr.ts// Switch to Spanish
await cohoLocale.setLocale('es');
// Switch back to English
await cohoLocale.setLocale('en');
// Check current locale
cohoLocale.getLocale();
// See available locales
cohoLocale.availableLocales;http://localhost:3000/?locale=es
localStorage.removeItem('locale');Main configuration file:
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": ["es"],
"tsConfig": "tsconfig.localize.json",
"output": {
"mode": "runtime",
"outputDir": "src/generated/locales",
"localeCodesModule": "src/generated/locale-codes.ts"
},
"interchange": {
"format": "xliff",
"xliffDir": "xliff"
}
}Configures runtime locale switching:
-
getLocale()- Returns current locale code -
setLocale(locale)- Switches locale (returns Promise) -
getPreferredLocale()- Gets saved/detected locale preference -
saveLocalePreference(locale)- Persists to localStorage
-
src/generated/locale-codes.ts- ExportssourceLocale,targetLocales,allLocales -
src/generated/locales/es.ts- Spanish translations module
-
xliff/es.xlf- Spanish translations in XLIFF format
-
Always use
@localized()decorator on components withmsg()calls -
Use
strtag for strings with${interpolation} - Keep strings simple - avoid complex HTML in translatable strings
-
Provide context - use the
descoption for ambiguous strings:msg('Save', { desc: 'Button to save changes' });
- Run extract after adding strings - keeps XLIFF files in sync
-
Don't edit generated files - they're overwritten by
lit-localize build
- Ensure component has
@localized()decorator - Check that
msg()is imported from@lit/localize
- We use a separate
tsconfig.localize.jsonwithes2022lib - This is referenced in
lit-localize.json
- Use explicit imports in
localeModulesobject instead of template literals - Each locale needs its own import statement
- Run
npx lit-localize buildafter editing XLIFF files - The build generates new TypeScript modules from XLIFF
Tests set the locale to English before the app loads to ensure consistent assertions:
// In tests/test-utils.ts
await page.addInitScript(() => {
localStorage.setItem('locale', 'en');
});This ensures tests looking for English strings like "followed you" will pass regardless of the test machine's browser language setting.
To test that locale switching works:
test('can switch locale', async ({ page }) => {
await bootstrapApp(page);
// Switch to Spanish via console
await page.evaluate(async () => {
await window.cohoLocale.setLocale('es');
});
// Assert Spanish text appears
await expect(page.locator('...')).toContainText('Spanish text');
});