Translation System¶
Overview¶
eXeLearning uses XLF (XLIFF) files for internationalization. Translation files are stored in the translations/ directory and loaded at server startup.
Supported Languages¶
The application supports these interface languages:
| Code | Language |
|---|---|
en |
English (default) |
es |
Español |
ca |
Català |
va |
Valencià |
eu |
Euskara |
gl |
Galego |
pt |
Português |
eo |
Esperanto |
ro |
Română |
Additional locales are available for exported content packages (see src/services/translation.ts for the full list).
Translation Files¶
Translations are stored as XLF files in translations/:
translations/
├── messages.en.xlf
├── messages.es.xlf
├── messages.ca.xlf
├── messages.eu.xlf
├── messages.gl.xlf
├── messages.pt.xlf
├── messages.eo.xlf
├── messages.ro.xlf
└── messages.va.xlf
Using Translations¶
In TypeScript (Backend)¶
import { trans } from '../services/translation';
// Simple translation
const message = trans('welcome.message');
// With parameters
const greeting = trans('hello.user', { name: 'John' });
// Parameters support both %param% and {param} formats
In Nunjucks Templates¶
{{ trans('page.title') }}
{{ trans('welcome.user', { name: user.name }) }}
In JavaScript (Frontend)¶
// Using the __() or t() helper functions
const message = __('error.not_found');
const title = t('page.title');
Translation Commands¶
Extract New Translation Keys¶
Scan source files for translation function calls and add new keys to XLF files:
# Extract keys for all locales
bun cli translations
# Extract for a specific locale
bun cli translations --locale=es
# Only extract (skip cleanup)
bun cli translations --extract-only
Clean XLF Files¶
Remove invalid entries and clean up formatting:
bun cli translations --clean-only
Using Make¶
make translations
Extraction Sources¶
The extractor scans these patterns:
| Directory | Extensions | Patterns |
|---|---|---|
src/ |
*.ts |
trans('key'), __('key'), t('key') |
views/ |
*.njk |
trans('key'), __('key'), t('key') |
public/app/ |
*.js |
trans('key'), __('key'), t('key') |
Adding a New Language¶
- Add the locale to
LOCALESinsrc/services/translation.ts:
export const LOCALES: Record<string, string> = {
en: 'English',
es: 'Español',
fr: 'Français', // New language
// ...
};
- Create the XLF file:
# Copy English as a starting point
cp translations/messages.en.xlf translations/messages.fr.xlf
- Edit the new XLF file:
- Update
target-languageattribute in the<file>element -
Translate the
<target>elements -
Run extraction to add any missing keys:
bun cli translations --locale=fr
XLF File Format¶
Translation entries use the standard XLIFF format:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="es" datatype="plaintext">
<body>
<trans-unit id="abc123" resname="welcome.message">
<source>Welcome to eXeLearning</source>
<target>Bienvenido a eXeLearning</target>
</trans-unit>
</body>
</file>
</xliff>
Locale Detection¶
The server detects the user's locale from:
- User preference (stored in session/profile)
Accept-LanguageHTTP header- Default locale (
en)
import { detectLocaleFromHeader, setLocale } from '../services/translation';
// Auto-detect from request
const locale = detectLocaleFromHeader(request.headers.get('accept-language'));
setLocale(locale);
Best Practices¶
- Use descriptive, hierarchical keys:
error.file.not_foundinstead oferr1 - Keep translations consistent across files
- Run
bun cli translationsafter adding new translatable strings - Test the UI in multiple languages during development