Translations
The Domain Locker app uses ngx-translate for translations, enabling multi-language support. This section explains how to implement translations in your components, use translations in TypeScript files, and add new languages.
Using Translations in Components
To use translations in a component, import the TranslateModule and add it to the component's imports:
import { TranslateModule } from '@ngx-translate/core';Example: Inline Template
Here is a minimal example of using translations in a template:
@Component({
selector: 'app-language-switcher',
standalone: true,
imports: [TranslateModule],
template: `
<div>
<label *ngFor="let lang of languages">
<input
type="radio"
[value]="lang.code"
[(ngModel)]="selectedLanguage"
(change)="onLanguageChange(lang.code)"
/>
{{ lang.flag }} {{ lang.name }}
</label>
</div>
`,
})
export class LanguageSwitcherComponent {
languages = [
{ code: 'en', name: 'English', flag: '🇬🇧' },
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
];
selectedLanguage = 'en';
constructor(private translationService: TranslationService) {}
onLanguageChange(langCode: string) {
this.translationService.switchLanguage(langCode);
}
}Using Translations in HTML
To translate strings in HTML, use the translate pipe:
<h2>{{ 'HOME.SUBHEADINGS.DOMAINS' | translate }}</h2>Using Translations in TypeScript
To translate strings in TypeScript, use the TranslationService:
constructor(private translationService: TranslationService) {}
someFunction() {
const translatedText = this.translationService.translateService.instant('HOME.SUBHEADINGS.DOMAINS');
console.log(translatedText); // Outputs the translated string
}Example with Parameters
You can pass dynamic parameters to translations:
const message = this.translationService.translateService.instant('TAGS.SUMMARY', { count: 5 });
console.log(message); // Outputs: "Showing 5 tags -"Adding a New Language
- Create a Language File: Add a new file for the language in
source/src/assets/i18n/. For example, for German, createde.json:
{
"NAV": {
"DOMAINS": "Domains",
"INVENTORY": "Inventar"
},
"HOME": {
"SUBHEADINGS": {
"DASHBOARD": "Dashboard",
"DOMAINS": "Domains"
}
}
}- Register the language in two places. Both lists must contain only languages that
have a file, or picking the language loads nothing and every key renders as
[KEY]:
// source/src/app/utils/translation-loader.factory.ts — what `?lang=` accepts
- export const AVAILABLE_LANGS = ['vi', 'en', 'de'];
+ export const AVAILABLE_LANGS = ['vi', 'en', 'de', 'fr'];
// source/src/app/services/translation.service.ts — what the picker shows
availableLanguages = [
{ code: 'vi', name: 'Tiếng Việt', flag: '🇻🇳' },
+ { code: 'fr', name: 'Français', flag: '🇫🇷' },
];Test the Language: Switch to the new language using the language switcher, or append
?lang=frto the URL — which the server honours too, so the served HTML is already in that language.Run the audit:
npm run i18n:auditchecks that the new file has exactly the same keys asen.jsonwith no empty values, and reports template strings that are still hard-coded.
Notes
- Default language is Vietnamese.
DEFAULT_LANGinsource/src/app/utils/translation-loader.factory.tsis the single source of truth; the<html lang>attribute and the SSR translation endpoint follow it. With no?lang=and no stored preference, the server renders Vietnamese. - Language resolution order:
?lang=→ stored preference (browser only) →DEFAULT_LANG.Accept-Languageis deliberately not consulted: the browser has no equivalent input at that point, so honouring it on the server alone would make the server render one language and hydration replace it with another. - Dynamic Language Switching: Changes take effect immediately after calling
switchLanguage. - Fallback Mechanism: Missing translations return the key wrapped in square brackets (e.g.,
[MISSING.KEY]). - Auditing coverage:
npm run i18n:auditruns two checks — untranslated template strings and key parity — and exits non-zero on either. It is intentionally not part ofnpm run hold-my-beer: most templates still carry hard-coded English, so it would block every unrelated change until that work is finished.scripts/i18n-audit-allowlist.jsonholds the genuine non-strings; add to it only for brand names, units and code samples.
flowchart TD
subgraph Init_Translation
Start["App Start (Client/Server)"] --> LanguageInit["languageInitializerFactory"]
LanguageInit -->|Platform: Browser| GetLocalStorageLang["?lang=, then localStorage.getItem('language')"]
LanguageInit -->|Platform: Server| ServerLang["?lang= from request.originalUrl"]
GetLocalStorageLang --> SetLang["translate.use(lang), else DEFAULT_LANG ('vi')"]
ServerLang --> SetLang
SetLang --> SetHtmlLang["document.documentElement lang = lang"]
end
subgraph TranslateLoader
TranslateService --> ServerSafeTranslateLoader
ServerSafeTranslateLoader -->|isPlatformBrowser| HTTPFetch["HttpClient GET /i18n/{lang}.json"]
ServerSafeTranslateLoader -->|isPlatformServer| FSRead["fs.readFileSync('/assets/i18n/{lang}.json')"]
HTTPFetch --> TranslationsLoaded
FSRead --> TranslationsLoaded
end
subgraph TranslationService
ComponentUsesTranslate --> TranslationServiceSwitch["switchLanguage(code)"]
TranslationServiceSwitch -->|Valid Code| UseTranslate["translate.use(code)"]
UseTranslate --> StoreLang["localStorage.setItem('language')"]
ComponentTS --> InstantTranslate["translateService.instant('KEY')"]
InstantWithParams["instant('TAGS.SUMMARY', { count })"] --> InstantTranslate
end
subgraph Fallbacks
MissingKey["Missing Translation Key"] --> CustomHandler["CustomMissingTranslationHandler"]
CustomHandler --> ShowFallback["Return '[KEY]' and warn"]
end
subgraph Template_Usage
TranslatePipe["{{ 'HOME.SUBHEADINGS.DOMAINS' | translate }}"] --> TranslateService
end
Start --> TranslateService
TranslateService --> TranslatePipe
TranslateService --> ComponentTS
TranslateService --> ComponentUsesTranslate
TranslationsLoaded --> TranslateService
TranslateService --> MissingKey