Skip to content

Internationalization Integration Guide

RTC Agent builds a complete internationalization system on @lit/localize, supporting runtime language switching without page reloads.

The component determines the initial language at startup according to the following priority:

  1. HTML attribute: The lang attribute set on <rtc-agent lang="en-US"> (highest priority)
  2. localStorage: The user’s choice saved under the key rtc-agent-locale
  3. Browser language: navigator.language, used if it’s in the supported list
  4. Default: Falls back to the source language zh-CN

Call initLocale() when the component starts to initialize the locale:

import { initLocale } from '@rtc-agent/component';
// Call at application startup
await initLocale();

Initialization logic:

  1. Check if there’s a saved language in localStorage (key: rtc-agent-locale)
  2. Check if browser language navigator.language is in the supported list
  3. Fall back to source language zh-CN
  4. Sync document.documentElement.lang attribute (screen reader pronunciation rules)

lit-localize.json defines language configuration:

{
"sourceLocale": "zh-CN",
"targetLocales": ["en-US"],
"tsConfig": "tsconfig.json",
"output": {
"mode": "runtime",
"localeCodesModule": "src/.generated/locale-codes.ts"
}
}

Switch languages at runtime:

import { switchLocale, type SupportedLocale } from '@rtc-agent/component';
// Switch to English
await switchLocale('en-US');
// Switch back to Chinese
await switchLocale('zh-CN');

switchLocale() behavior:

StepOperation
1Call @lit/localize’s setLocale() to load language pack
2Save to localStorage (key: rtc-agent-locale)
3Update document.documentElement.lang attribute
4All @localized() components automatically re-render

Get the currently active locale (synchronous):

import { getLocale } from '@rtc-agent/component';
const current = getLocale(); // 'zh-CN' | 'en-US'

Persist locale preference to localStorage without triggering a locale switch. Useful for saving user choice before the component is mounted:

import { persistLocale } from '@rtc-agent/component';
persistLocale('en-US');
Language CodeLanguageType
zh-CNSimplified ChineseSource language
en-USEnglishTarget language
// Type-safe
type SupportedLocale = 'zh-CN' | 'en-US';
// Type guard
import { isValidLocale } from '@rtc-agent/component';
if (isValidLocale(userInput)) {
await switchLocale(userInput);
}

It is important to note that the component contains two categories of text: UI text (controlled by the i18n system, switches with switchLocale()) and Agent-Facing messages (always in English, unaffected by the UI language).

Agent-Facing messages are text written to the virtual file system for the LLM to read, for example:

  • Parameter validation error messages (error prompts referenced in /functions/INDEX.md)
  • Auto-generated function documentation (AGENT.md, /functions/*.md)
  • Scenario index (/scenarios/INDEX.md)

These messages are uniformly in English because Agent prompts and tool call protocols are in English, and mixing in other languages would cause inconsistent LLM parsing. Host applications do not need to translate these messages during integration.

Use the @localized() decorator to mark components that need to respond to language changes:

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { localized, msg } from '@lit/localize';
import { consume } from '@lit/context';
import { localeContext, type LocaleContextValue } from '@rtc-agent/component';
@localized()
@customElement('my-component')
export class MyComponent extends LitElement {
@consume({ context: localeContext, subscribe: true })
@state()
private _localeCtx: LocaleContextValue = { /* ... */ };
render() {
// Reference locale to ensure re-render on language switch
void this._localeCtx.locale;
return html`
<div>${msg('Welcome to RTC Agent')}</div>
<p>${msg('This is an internationalized component')}</p>
`;
}
}

msg() marks text that needs translation:

import { msg, str } from '@lit/localize';
// Simple text
msg('Hello World')
// Text with variables
const name = 'User';
msg(str`Hello, ${name}!`)
// Text with HTML (needs escaping)
msg(html`Click <a href="/help">${msg('Help')}</a>`)

Get the locale context via @lit/context:

import { consume } from '@lit/context';
import { localeContext, type LocaleContextValue } from '@rtc-agent/component';
@consume({ context: localeContext, subscribe: true })
@state()
private _localeCtx: LocaleContextValue = {
locale: 'zh-CN',
setLocale: async () => {},
locales: ['zh-CN', 'en-US'],
};

Wrap text that needs translation with msg() in components:

render() {
return html`
<h1>${msg('Settings')}</h1>
<p>${msg('Choose interface language')}</p>
`;
}

Run the extract command to generate XLIFF files:

终端窗口
npm run localize:extract

This generates translation entries in xliff/en-US.xlf:

<trans-unit id="s8ef06a6080e58381">
<source>Settings</source>
<target state="new">Settings</target>
<note from="lit-localize">Settings panel title</note>
</trans-unit>

Edit xliff/en-US.xlf, replacing <target> with the translated text:

<trans-unit id="s8ef06a6080e58381">
<source>Settings</source>
<target>Settings</target>
</trans-unit>

Run the build command to generate compiled language packs:

终端窗口
npm run localize:build

This generates optimized translation mappings in src/locales/en-US.ts.

RTC Agent provides a proportional scaling font size system based on --rtc-font-size-user.

rtc-agent {
/* Set base font size (12-24px) */
--rtc-font-size-user: 16px;
}

All font size tokens scale proportionally to the base font size:

TokenFormula12px14px (default)16px18px20px
--rtc-font-size-xsbase * 0.85710px12px14px15px17px
--rtc-font-size-smbase * 0.92911px13px15px17px19px
--rtc-font-size-basebase12px14px16px18px20px
--rtc-font-size-mdbase * 1.14314px16px18px21px23px
--rtc-font-size-lgbase * 1.28615px18px21px23px26px
--rtc-font-size-xlbase * 1.42917px20px23px26px29px
--rtc-font-size-2xlbase * 1.71421px24px27px31px34px

The settings panel provides a font size adjustment control:

// In rtc-settings-layout
private _renderAppearance() {
return html`
<div class="setting-row">
<label for="font-size-input">${msg('Font Size')}</label>
<input
type="number"
id="font-size-input"
min="12"
max="24"
step="1"
.value=${String(this._settingsCtx.state.appearance.fontSize)}
@change=${(e: Event) => {
const size = Number((e.target as HTMLInputElement).value);
this._updateSetting('appearance', { fontSize: size });
}}
/>
<span>px</span>
</div>
`;
}

SettingsController automatically applies the font size to the document root:

// SettingsController._applyFontSize()
document.documentElement.style.setProperty(
'--rtc-font-size-user',
`${clamped}px`
);

This ensures all components in Shadow DOM can inherit the font size setting.

PracticeDescription
✅ Always use msg()All user-visible text should be wrapped in msg()
✅ Reference localeReference this._localeCtx.locale in render() to ensure re-rendering
✅ Use @localized()Components that need to respond to language changes must add this decorator
✅ Type safetyUse SupportedLocale type and isValidLocale() guard
❌ Don’t hardcodeAvoid hardcoding user-visible text outside msg()
❌ Don’t call _setLocale directlyUse switchLocale() to ensure persistence and DOM sync