Localizing the UI
The HitKeep dashboard is fully translated into English, German, Spanish, French, and Italian — out of the box, no plugins required. The selected language is stored in the database and follows the user across sessions and devices.
Supported Languages
Section titled “Supported Languages”| Language | Code | Translation file |
|---|---|---|
| English | en | frontend/dashboard/public/i18n/en.json |
| German | de | frontend/dashboard/public/i18n/de.json |
| Spanish | es | frontend/dashboard/public/i18n/es.json |
| French | fr | frontend/dashboard/public/i18n/fr.json |
| Italian | it | frontend/dashboard/public/i18n/it.json |
How Language Selection Works
Section titled “How Language Selection Works”Users change the interface language in Settings → Preferences. The selected locale is saved to user_preferences.default_locale in the database — it is not a browser-only cookie, so the choice persists when the user logs in from a different device.
Switching language is instant: the dashboard re-renders without a page reload, powered by Transloco.
Numbers and dates automatically reformat to match the locale (e.g. 1.234,56 in German, 1,234.56 in English) via @jsverse/transloco-locale.
Translation File Format
Section titled “Translation File Format”Each locale is a single JSON file with nested keys. en.json is the reference — all other files must contain the same key structure with translated values.
{ "nav": { "dashboard": "Dashboard", "utm": "UTM", "utmBuilder": "UTM Builder" }, "common": { "actions": { "save": "Save", "cancel": "Cancel", "clearAll": "Clear all" } }, "dashboard": { "title": "Dashboard", "noData": "No data for this period." }}The top-level namespaces map to feature areas:
| Namespace | Contents |
|---|---|
admin | Admin panel: users, sites, IP exclusions |
common | Shared buttons, column headers, empty states, filters |
dashboard | Dashboard page and chart labels |
funnels | Funnel analytics |
goals | Goal tracking |
integration | API clients and API reference pages |
login | Login and setup screens |
nav | Sidebar navigation labels and ARIA strings |
preferences | Language and theme settings |
settings | Profile, security (2FA, passkeys), email reports |
share | Public shared dashboard |
sites | Site management |
utm | UTM analytics page |
utmBuilder | UTM link builder tool |
Interpolation
Section titled “Interpolation”Some strings contain runtime variables wrapped in {{ }}. Leave the variable names exactly as-is — translate only the surrounding text.
// ✅ Correct"filters": { "referrer": "Referred by {{ value }}"}
// ❌ Wrong — variable name changed"filters": { "referrer": "Verwiesen von {{ wert }}"}Pluralisation
Section titled “Pluralisation”Transloco uses ICU-style plural rules for strings that differ by count. When present, they look like:
"itemCount": "{ count, plural, =0 {No items} =1 {One item} other {# items} }"Translate the label text inside each plural branch; keep the count, plural, branch keys, and # placeholder unchanged.
Contributing a Translation
Section titled “Contributing a Translation”Adding a New Language
Section titled “Adding a New Language”1. Fork and clone the repository.
git clone https://github.com/pascalebeier/hitkeep.gitcd hitkeep2. Copy the English reference file.
cp frontend/dashboard/public/i18n/en.json \ frontend/dashboard/public/i18n/{lang}.jsonReplace {lang} with a BCP 47 base tag — e.g. pt for Portuguese, nl for Dutch, ja for Japanese.
3. Translate all values.
Open {lang}.json in your editor and translate every string value. Keep all keys unchanged.
"save": "Save"
// pt.json"save": "Salvar"4. Register the language in app.config.ts.
Add the new code to both availableLangs and langToLocaleMapping:
provideTransloco({ config: { availableLangs: ['en', 'de', 'es', 'fr', 'it', 'pt'], // add 'pt' defaultLang: 'en', // ... },}),provideTranslocoLocale({ langToLocaleMapping: { // existing entries … pt: 'pt-BR', // or 'pt-PT' — pick the most common regional locale 'pt-BR': 'pt-BR', }}),A locale string maps to a standard IETF locale used by Intl APIs to format numbers and dates correctly.
5. Test locally.
cd frontend/dashboardnpm installnpm startOpen the dashboard, navigate to Settings → Preferences, and switch to your new language. Verify labels, number formats, and date formats all look correct.
6. Submit a pull request.
Include both the new {lang}.json file and the app.config.ts change in your PR. The title should follow Conventional Commits:
feat(i18n): add Portuguese (pt) translationFixing an Existing Translation
Section titled “Fixing an Existing Translation”Found a mistranslation or an untranslated key?
- Locate the key in
en.jsonto confirm the English source. - Edit the same key in the target locale file.
- Submit a PR with title
fix(i18n): correct <lang> translation for <key>.
You do not need to rebuild anything — translation files are loaded at runtime as static JSON.
Keeping Translations in Sync
Section titled “Keeping Translations in Sync”When a new HitKeep feature ships, new keys are added to en.json first. Existing locale files become incomplete until a contributor translates the additions.
To find keys that are present in English but missing from another locale, run:
node -e "const en = require('./frontend/dashboard/public/i18n/en.json');const target = require('./frontend/dashboard/public/i18n/de.json');
function diff(a, b, path = '') { for (const k of Object.keys(a)) { const p = path ? path + '.' + k : k; if (!(k in b)) { console.log('MISSING:', p); } else if (typeof a[k] === 'object') diff(a[k], b[k], p); }}diff(en, target);"Replace de.json with the locale you want to audit.
Tech Stack
Section titled “Tech Stack”| Layer | Library | Role |
|---|---|---|
| Translation strings | Transloco | Load JSON files, switch language at runtime |
| Number & date formatting | @jsverse/transloco-locale | Localises Intl.NumberFormat / Intl.DateTimeFormat to the active lang |
| Persistence | user_preferences.default_locale in DuckDB | Stores the user’s choice server-side across sessions |