v0.21

localization

introduction

dframework provides a lightweight localization system for building multilingual applications. translations are stored as json files organized by locale, and the framework provides helpers for retrieving translated strings in both controllers and views. locale detection happens automatically from cookies and the Accept-Language header, and the active locale can be changed per request or persisted to the user's session.

defining translations

directory structure

translation files are stored in the lang directory at the root of your project. each locale has its own subdirectory named with the locale code. inside each locale directory, you create json files that group related translation keys.

1lang/
2├── en/
3│ ├── common.json
4│ ├── auth.json
5│ └── dashboard.json
6├── ja/
7│ ├── common.json
8│ ├── auth.json
9│ └── dashboard.json
10└── es/
11 ├── common.json
12 └── auth.json

translation files

each translation file is a standard json object mapping keys to translated strings.

1{
2 "welcome": "welcome to dframework",
3 "goodbye": "see you next time",
4 "greeting": "hello, welcome back"
5}
1{
2 "welcome": "dframeworkへようこそ",
3 "goodbye": "またお会いしましょう",
4 "greeting": "こんにちは、おかえりなさい"
5}

nested keys

translation files support nested objects. you access nested keys using dot notation.

1{
2 "buttons": {
3 "save": "save changes",
4 "cancel": "cancel",
5 "delete": "delete permanently"
6 },
7 "messages": {
8 "success": "operation completed",
9 "error": "something went wrong"
10 }
11}
1await t(req, 'common.buttons.save');
2// "save changes"

retrieving translations

the t helper

the t() function is the primary way to retrieve translations. it is available globally. it accepts the current request object, a dot notation key, and an optional fallback value.

1const welcome = await t(req, 'common.welcome');
2const missing = await t(req, 'common.nonexistent', 'default text');

the first segment of the key is the filename (without the .json extension), and the remaining segments form the path within the json object.

key file path
common.welcome lang/{locale}/common.json welcome
auth.errors.invalid lang/{locale}/auth.json errors.invalid
dashboard.stats.users lang/{locale}/dashboard.json stats.users

the function is asynchronous because it reads translation files from disk on first access.

in views

in view templates, use the @t() directive. the framework automatically resolves the current locale from the request context.

1<h1>@t('common.welcome')</h1>
2<button>@t('common.buttons.save')</button>

the @t() directive is syntactic sugar for calling the t() helper with the current request object. you do not need to pass the request manually.

fallback behavior

if a translation key is not found, the t() function returns the fallback value if one was provided. if no fallback was given, it returns the raw key string itself. this ensures your application never displays blank text due to a missing translation.

1await t(req, 'common.missing_key');
2// returns "common.missing_key"
3
4await t(req, 'common.missing_key', 'fallback text');
5// returns "fallback text"

if the translation file itself does not exist for the current locale, the function also falls back gracefully.

locale detection

detection order

dframework detects the user's locale automatically using the following priority:

  1. locale cookie — if a locale cookie is present and contains a valid locale code (2-10 alphanumeric characters), it is used immediately
  2. accept-language header — if no cookie is found, the framework parses the Accept-Language http header and uses the primary language code
  3. default locale — if neither source provides a valid locale, the configured default locale is used

this detection happens automatically during request processing. the detected locale is stored on the request object as req.locale.

default locale

the default locale is configured in your config/app.js file under the locale key.

1// config/app.js
2export default {
3 locale: 'en'
4};

if no default is configured, the framework falls back to 'en'.

setting the locale

persistent locale

use the Locale.set() function to change the user's locale. by default, the change is persisted by writing a locale cookie and storing the preference in the user's session.

1import { Locale } from 'dframework';
2
3Locale.set('ja');

or use the globally available function:

1Locale.set('es');

the cookie is written immediately to ensure subsequent navigations respect the new locale even before the session is synced. the cookie uses SameSite=Lax, the secure flag in non local environments, and the root path /.

the locale value must match the pattern /^[a-zA-Z]{2,10}(-[a-zA-Z]{2,8})?$/. invalid values throw an error.

request-only locale

if you need to change the locale for only the current request without persisting it, pass true as the second argument.

1Locale.set('fr', true);

this sets req.locale for the duration of the request but does not write a cookie or update the session. this is useful for previewing content in different languages or for api endpoints that accept a locale parameter.

reading the locale

you can read the current locale from the request context. the Locale export provides access to locale management methods.

1import { Locale } from 'dframework';
2
3const currentLocale = Locale.get();

you can also read the default locale directly.

1const defaultLocale = Locale.getDefault();

caching and hot reload

translation files are cached in memory after their first read. subsequent calls to t() with the same locale and file combination return the cached dictionary without hitting the filesystem.

in the local environment, the framework sets up file watchers on each loaded translation file. when you edit a translation file and save it, the cache for that specific file and locale is automatically invalidated. the next call to t() will reread the updated file from disk. this provides instant feedback during development without restarting the server.

the file watchers are automatically cleaned up when the application shuts down. in production, no file watchers are created and the cache persists for the lifetime of the process.