Problem: BibleWeb serves Dutch-speaking users who may not be comfortable navigating an English-only interface. The entire app UI — menus, buttons, labels, messages — needs a Dutch version.
Solution: A complete i18n (internationalization) system with Dutch translations for every UI string in the app. Users toggle between English and Dutch in Settings. This is independent of the Bible translation — you can read BSB English with a Dutch UI, or Statenvertaling with an English UI.
Not included: Other UI languages (German, French, etc.). The system supports it architecturally, but only English and Dutch are implemented.
Users can switch the entire app interface to Dutch via a toggle in Settings. All navigation labels, buttons, headers, context menus, book names, theme names, and parable descriptions switch to Dutch.
User flow:
What gets translated:
Edge cases:
Settings toggle:
No visual distinction — Dutch UI looks identical to English, just with translated text. All layouts, colors, and icons remain the same.
i18n system (apps/web/src/lib/i18n/):
index.ts — public API. Maintains a cache of locale modules. Both en and nl are statically imported (pre-populated at module load).t(key, lang, ...args) — looks up a string key from the active locale, falls back to English. Supports {0} / {1} placeholder formatting.getBookName(id, lang), getBookShortName(id, lang), getPericopeTitle(englishTitle, lang), getThemeName(englishName, lang), getParableDescription(...), getPericopeDescription(...)Locale files:
en.ts — default export: English string map (~400+ lines). Named exports: bookNames (66 entries), bookShortNames, pericopeTitles, themeNames, parableDescriptions, pericopeDescriptionsnl.ts — same structure in Dutch. Complete translations of all UI strings. Dutch book names (e.g., book 40 = "Mattheüs", book 43 = "Johannes")Settings persistence:
settings.language: 'en' | 'nl' in settings.svelte.ts, persisted to localStorage under bibleweb_settingssetLanguage(lang) writes to state and persistsUsage pattern in components:
import { settings } from '$lib/stores/settings.svelte';
import { t } from '$lib/i18n';
const lang = $derived(settings.language);
// Then in template: {t('nav.search', lang)}
Server-side: +page.server.ts returns both bookName (English) and bookNameNl (Dutch) for the reader page title.
Files:
apps/web/src/lib/i18n/index.ts — i18n engine, t() function, helper functionsapps/web/src/lib/i18n/en.ts — English strings + book names (~400+ lines)apps/web/src/lib/i18n/nl.ts — Dutch strings + book names (~400+ lines)apps/web/src/lib/stores/settings.svelte.ts — settings.language, setLanguage()apps/web/src/routes/(app)/settings/+page.svelte — EN/NL toggle UICurrent: DONE Milestone: Foundation (pre-v1) Priority: Core — essential for Dutch-speaking target audience
History:
'klein', 'middel', 'groter', 'grootst') before internationalizationDependencies: