Native Localization & Dynamic CSS
Gameface’s localization system bypasses the JSON-file approach common in web frameworks. The frontend declares translation keys directly in HTML or requests them in JavaScript. The engine resolves those keys to strings at runtime using a localization manager registered on the C++ side. Your job as a frontend developer is to annotate the HTML correctly, handle the edge cases where declarative translation doesn’t work, and write CSS that survives the layout shock of text expanding to twice its English length.
How Gameface Localization Works
Section titled “How Gameface Localization Works”Traditional web apps load translation files (JSON, .po, YAML) in the browser and look up keys client-side. Gameface works differently: the game engine owns the locale and the string table. The frontend never touches translation files directly. It requests a translation by key, and the engine returns the correct string for the current language.
This architecture has a concrete consequence for how you work: all locale state lives on the engine side. When the player changes language in the settings menu, the engine updates its locale internally and the UI must ask the engine to re-translate everything. The frontend does not detect locale changes on its own.
Translating HTML with data-l10n-id
Section titled “Translating HTML with data-l10n-id”The declarative way to localize an HTML element is to add a data-l10n-id attribute to it. The engine calls the localization manager’s Translate function using the attribute value as the key, and replaces the element’s content with the returned string.
import Button from '@components/Basic/Button/Button';import TextBlock from '@components/Basic/TextBlock/TextBlock';
const LocalizedUI = () => ( <> <Button attr:data-l10n-id="TextStartGameBtn">Start Game</Button> <TextBlock attr:data-l10n-id="WelcomeMessage">Welcome, player!</TextBlock> <TextBlock attr:data-l10n-id="HudObjectiveLabel">Current Objective</TextBlock> </>);<button data-l10n-id="TextStartGameBtn">Start Game</button><p data-l10n-id="WelcomeMessage">Welcome, player!</p><span data-l10n-id="HudObjectiveLabel">Current Objective</span>The default text inside each element is intentional. When the localization manager cannot find a key (say, a translator forgot to include it), the element’s original content is left untouched. That fallback makes it obvious during development which strings are missing without crashing or producing empty labels.
Translation is not limited to elements present in the initial HTML. Elements injected dynamically into the DOM are translated automatically when inserted:
import { createSignal, Show } from 'solid-js';import Button from '@components/Basic/Button/Button';
const [showResume, setShowResume] = createSignal(false);
const MenuList = () => ( <Show when={showResume()}> <Button attr:data-l10n-id="TextResumeBtn">Resume</Button> </Show>);const container = document.getElementById('menu-list');// The localization manager will translate this button's label on insertioncontainer.innerHTML = '<button data-l10n-id="TextResumeBtn">Resume</button>';Translation on the Fly with engine.translate()
Section titled “Translation on the Fly with engine.translate()”Two situations make data-l10n-id unsuitable:
-
Input and textarea elements. The value of
<input>and<textarea>is dynamic. Ifengine.reloadLocalization()fires while a player is typing, it wipes thedata-l10n-idelement’s content, destroying whatever the player had entered. Useengine.translate()for placeholder text and labels associated with form fields. -
JavaScript framework components. React, SolidJS, and similar frameworks own the DOM and reconcile it on their schedule. A
data-l10n-idattribute may get removed or ignored when the framework re-renders. Useengine.translate()inside your component’s rendering logic instead.
engine.translate() takes a key string and returns the translated value synchronously:
const submitLabel = engine.translate('TextSubmitBtn');submitButton.textContent = submitLabel;For building composite strings (a translated label followed by a dynamic value), concatenate after the translate call:
const contactLabel = engine.translate('ContactNumber');const contactEl = document.getElementById('profile-contact');contactEl.appendChild(document.createTextNode(`${contactLabel}: ${playerContact}`));Changing the Locale Dynamically
Section titled “Changing the Locale Dynamically”Gameface does not track the current locale internally and will not automatically re-translate content when the game switches languages. You must trigger re-translation manually by calling engine.reloadLocalization().
engine.reloadLocalization() iterates all elements in the document that have a data-l10n-id attribute and calls engine.translate() on each one, replacing their content with the newly translated string for the updated locale. Elements that used engine.translate() directly in JavaScript are not touched by this call. You are responsible for updating them separately.
A typical language change flow looks like this: the player selects a language in the settings menu, the UI notifies the engine, the engine updates its locale internally, and then the UI calls engine.reloadLocalization() once that operation completes.
import { createSignal } from 'solid-js';import TextBlock from '@components/Basic/TextBlock/TextBlock';import Button from '@components/Basic/Button/Button';
const [contactLabel, setContactLabel] = createSignal('');const [submitLabel, setSubmitLabel] = createSignal('');let currentContactNumber = '';
function refreshDynamicLabels() { setContactLabel(engine.translate('ContactNumber') + ': ' + currentContactNumber); setSubmitLabel(engine.translate('TextSubmitBtn'));}
const ProfileFooter = () => ( <> <TextBlock id="profile-contact">{contactLabel()}</TextBlock> <Button id="submit-btn">{submitLabel()}</Button> </>);// Called when the player confirms a language change in the settings menufunction changeLanguage(languageCode) { engine.call('ChangeLanguage', languageCode).then(() => { // The engine has updated its locale. Now re-translate all data-l10n-id elements. engine.reloadLocalization();
// Re-translate any elements that were set with engine.translate() directly refreshDynamicLabels(); });}
function refreshDynamicLabels() { document.getElementById('profile-contact').textContent = engine.translate('ContactNumber') + ': ' + currentContactNumber;
document.querySelector('#submit-btn').textContent = engine.translate('TextSubmitBtn');}The then() callback guarantees that engine.reloadLocalization() runs only after the engine has finished updating its locale. Calling it before the engine finishes would translate using the old locale.
CSS Optimizations for Localization
Section titled “CSS Optimizations for Localization”Font Fitting: Surviving Text Expansion
Section titled “Font Fitting: Surviving Text Expansion”A four-letter English word like “Save” can become a fifteen-character German string like “Speichern”. Fixed-width buttons and HUD labels that look correct in English will overflow in other locales.
The coh-font-fit-mode property instructs the layout engine to automatically adjust the rendered font size until the text fits within its container. The property accepts three values:
none: Default. No size adjustment.shrink: Reduces font size if text overflows the container, but never grows it beyond the declaredfont-size.fit: Both shrinks and grows text to fill the container as completely as possible.
For most localization scenarios, shrink is the correct choice. It guarantees buttons never overflow while preserving the visual size of shorter strings like English labels:
.menu-button-label { font-size: 24px; /* Starting point for the fitting algorithm */ coh-font-fit-mode: shrink; coh-font-fit-min-size: 14px; /* Never shrink below this size */}Use fit when visual consistency across languages matters more than text size variation. With fit, the engine grows short strings to fill the container and shrinks long ones, so all buttons carry text of roughly the same visual weight:
.hud-objective-label { /* Shorthand: mode | min-size | max-size */ coh-font-fit: fit 12px 28px; /* Always declare max-size explicitly to avoid engine warnings */}For a detailed walkthrough of all coh-font-fit-mode values, boundary properties, and the coh-font-fit shorthand, see the Auto-Scaling & Complex Text article.
Custom Media Queries for Language-Specific Styles
Section titled “Custom Media Queries for Language-Specific Styles”Sometimes auto-scaling is not enough. German compound words may require a completely different button layout. Japanese characters at the same font-size as Latin scripts may produce text that is too small. These cases need wholesale CSS rule changes, not just size adjustments.
Gameface supports custom media features that the game engine sets at runtime. The CSS you write is standard @media syntax, but the feature name and value are arbitrary strings that you define. The game engine activates or deactivates them via an engine-side API call.
A practical pattern is a language feature that mirrors the active locale:
/* Default styles (English) */.menu-button-label { font-size: 22px; coh-font-fit-mode: shrink; coh-font-fit-min-size: 14px;}
/* German-specific overrides: smaller base font, more horizontal padding */@media (language: de) { .menu-button-label { font-size: 18px; coh-font-fit-mode: shrink; coh-font-fit-min-size: 12px; }}
/* Japanese-specific overrides: larger base size for CJK character legibility */@media (language: ja) { .menu-button-label { font-size: 24px; coh-font-fit-mode: none; }}Custom media feature values for the same feature name are mutually exclusive. When the engine activates (language: de), the (language: ja) block automatically becomes inactive. Only one value per feature name can be active at a time.
You can also combine custom features with standard media features. This is useful for applying language-specific tweaks only at certain viewport sizes:
/* Apply German-specific font size only when the view is narrower than 400px */@media (language: de) and (max-width: 400px) { .settings-description { font-size: 13px; line-height: 1.3; }}Custom features are set per view by the game engine when the locale changes. The game engine team in your project should update the language feature value alongside the locale change. From the frontend side, call engine.reloadLocalization() to re-translate data-l10n-id elements simultaneously so the new CSS rules and translated strings take effect together:
function changeLanguage(languageCode) { engine.call('ChangeLanguage', languageCode).then(() => { // CSS media feature and translated strings update in the same frame engine.reloadLocalization(); refreshDynamicLabels(); });}© 2026 Coherent Labs. All rights reserved.