The expressjs.com website supports multiple languages through a combination of Astro routing, TypeScript utilities, and Crowdin integration.
We use Crowdin to manage our translations in multiple languages and to enable automatic translation with artificial intelligence. Since AI translations can be imperfect, we welcome community contributions to improve translation accuracy and quality.
- Request to join the Express.js Website project on Crowdin
- Select the language you want to translate
- Start translating in the Crowdin editor
Translation contributions must be made through Crowdin, not directly to the repository. Direct edits to translation files will be overwritten by Crowdin syncs.
To add a new language to the website, follow this process:
-
Request language support - Contact a website captain to request a new language in Crowdin. Only website maintainers can add new languages to the Crowdin project.
-
Crowdin will generate files - Once approved in Crowdin, translation files will be automatically created:
src/i18n/ui/[lang-code].json- UI strings- Content files in
src/content/docs/[lang-code]/
-
Update
src/i18n/locales.ts- Once files are created by Crowdin:- Add the language to the
languagesobject - Import the new language's JSON file from
src/i18n/ui/
- Add the language to the
en- Englishde- German (Deutsch)es- Spanish (Español)fr- French (Français)it- Italian (Italiano)ja- Japanese (日本語)ko- Korean (한국어)pt-br- Brazilian Portuguese (Português)zh-cn- Chinese Simplified (简体中文)zh-tw- Chinese Traditional (繁體中文)
The i18n system is built on these key components:
-
Locales Configuration (
src/i18n/locales.ts):- Defines all supported languages and their metadata
- Imports UI translation strings from JSON files
- Exports types for type-safe translations
-
i18n Utilities (
src/i18n/utils.ts):getLangFromUrl()- Extracts the language code from the URLuseTranslations()- Returns a translation function for the current language- Helper functions for language path manipulation
-
Routing (
src/pages/[lang]/[...slug].astro):- Dynamic routing with the
[lang]parameter for language selection - Supports URLs like
/en/api/,/fr/api/,/de/api/, etc.
- Dynamic routing with the
-
UI Translations (
src/i18n/ui/):- JSON files for each supported language (e.g.,
en.json,fr.json) - Contains UI strings like navigation labels, buttons, and UI elements
- Managed through Crowdin for easier translation
- JSON files for each supported language (e.g.,
---
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<button aria-label={t('nav.toggleMenu')}>Menu</button>
<h1>{t('common.welcome')}</h1>The Astro.url object automatically provides the current page URL, making it easy to extract the language code and get the appropriate translations.
UI strings are stored in JSON files in src/i18n/ui/. For example, src/i18n/ui/en.json:
{
"common": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"navigation": {
"docs": "Documentation",
"api": "API Reference"
}
}Documentation content is stored separately from UI translations:
- Docs:
src/content/docs/[lang-code]/[version]/- Versioned documentation pages - Pages:
src/content/pages/[lang-code]/- Global pages (non-versioned docs, resources, support)
The following content is currently not translated:
- API reference
- Blog posts
When adding new UI strings:
- Add the string to
src/i18n/ui/en.jsonfirst - The structure will be synced to Crowdin
- Translators will translate it through Crowdin
- Updated translations will be synced back
The crowdin.yml configuration file defines:
- Source files and their Crowdin translation targets
- Directory structure for translations
- API configuration for automation
When translations are completed in Crowdin:
- Files are automatically synced to the repository
- A bot automatically creates pull requests with the translation updates
Warning
Do not manually create or edit translation files (src/i18n/ui/*.json and src/content/docs/[lang-code]/*.*)
These files are automatically managed by Crowdin and will be overwritten if you make manual changes.
All translation updates must go through the Crowdin workflow.