QoreDB is used by developers who don't all speak English, and by a community that contributes translations. The interface is currently available in nine languages: English, French, Spanish, German, Brazilian Portuguese, Simplified Chinese, Japanese, Korean, and Russian. This level of coverage, in a desktop application maintained by a small team, did not require an elaborate translation framework or an external service. Everything fits into a minimal i18next configuration and nine JSON files embedded in the bundle.
This post describes how the system is structured, why this choice remains solid despite its simplicity, and what it deliberately avoids.
Why i18next and nothing else
The React ecosystem offers several internationalization libraries: react-intl (FormatJS), Lingui, i18next, or in-house solutions. The choice of i18next comes down to three concrete reasons.
First, the library has been stable for more than a decade and its API has changed very little. A long-term dependency in a local-first project cannot be a permanent construction site. Second, i18next exposes a simple key model (namespaces, interpolation, pluralization) without imposing a compiler or a specific build step. The translation files are standard JSON, readable by any non-developer contributor. Third, the react-i18next binding provides a useTranslation hook that stays unobtrusive in the components' code.
No in-house abstraction was added on top. The src/i18n.ts module fits in about sixty lines: it imports the JSON files, registers the resources, enables the language detector, and configures a fallback to English.
Nine languages, nine embedded JSON files
The translations live in src/locales/, one JSON file per language. Each reference file (French and English) contains more than three thousand lines covering every section of the interface: authentication, connections, SQL editor, data grid, notebook, settings, export dialogs, security panels. The keys are organized into objects nested by functional domain, which keeps the file navigable even at this volume.
These JSON files are imported statically in i18n.ts and therefore included in the Vite bundle at build time. No asynchronous loading, no network call at startup, no translation server to query. For a desktop application that has to work offline from the very first launch, this is the only coherent approach.
The size cost is real but modest: nine JSON files compress well and represent an order of magnitude less than a Tauri binary, whose weight comes mostly from the WebView runtime and the database drivers.
Client-side detection and switching
The active language is determined by i18next-browser-languagedetector, with an explicit resolution chain: querystring, then localStorage, then navigator. Concretely, a link like ?lang=fr forces the language immediately, which is useful for sharing a screenshot or a debugging session in a specific language. Otherwise, the user preference is read from localStorage under the i18nextLng key. On first launch, with no stored value, it falls back to the browser's language, then to English if no match is found.
Changing the language at runtime goes through a simple i18n.changeLanguage(lng) in the LanguageSwitcher component. i18next persists the value and triggers a re-render of every component that uses the hook. No reload of the application, no round trip to the Tauri backend.
Usage in the components' code
A component that needs translated text imports useTranslation, retrieves the t function, and calls t('auth.signIn') to access the key. Interpolation uses the {{variable}} syntax directly in the JSON values, which stays readable for translators. The hook is currently called in more than two hundred components across the project, which gives an idea of the interface's actual coverage.
There is no type generation from the JSON keys. Consistency across languages relies on the identical structure of the reference files and on manual reviews when contributions come in. This is a deliberate decision: the barrier to entry for a contributor who translates a menu or fixes a wording stays very low, with no build step to understand.
What this setup avoids
An i18n system can quickly become a project in its own right: ICU MessageFormat to cover exotic pluralization, automatic key extraction from the source code, a managed translation service, a synchronization workflow between translator teams and the repository. None of these elements was introduced in QoreDB, because none was necessary at this stage.
The interface of a database client has a specialized but finite vocabulary. The wordings don't need to be culturally adapted beyond a direct translation. Pluralization, where it exists (number of results, number of active connections), is handled by i18next's standard mechanisms. And the contributors who send translations prefer to open a JSON file in their editor rather than learn a dedicated tool.
The setup is also consistent with the local-first model: no usage data, no telemetry on language, no server profile that would store the preference. Language is a local setting, like the theme.
Conclusion
QoreDB's i18n system fits into one configuration module, nine JSON files, one hook, and one switcher component. That is enough to cover nine languages across the entire interface, without any dependency on an external service, without a specific build step, and without friction for contributors. The choice reflects the project's general approach: prefer a proven, right-sized stack over an ambitious abstraction that would be costly to maintain.
Stay updated on new releases
Subscribe to get product releases, new drivers notifications, and technical tutorials.

