| theme | @xebia/slidev-theme-xebia | |||
|---|---|---|---|---|
| transition | fade | |||
| addons |
|
|||
| download | false | |||
| browserExporter | true | |||
| record | false | |||
| editor | true | |||
| overviewSnapshots | false | |||
| glowEnabled | true | |||
| layout | cover | |||
| hideInToc | true | |||
| routeAlias | webappLocalization | |||
| background | /developer-in-words.png |
- Extract user-facing text from source code
- Replace text with translation keys
- Store translations in separate files (e.g., JSON)
- Use a localization library (e.g., react-intl)
```jsx
// Before
<h1>Welcome to our site!</h1>
```
```jsx
// Extract user-facing text from source code
// Replace text with translation keys or components
import { useTranslation } from 'react-intl'
function Home() {
const { t } = useTranslation()
return <h1>{t('welcome_message')}</h1>
}
// // Store translations in separate files (e.g., JSON)
// {
// "welcome_message": "Welcome to our site!"
// }
```
```jsx
// Use a localization library (e.g., react-intl)
// Load and switch languages dynamically
import { useTranslation } from 'react-intl'
function Home() {
const { t, i18n } = useTranslation()
return (
<>
<h1>{t('welcome_message')}</h1>
<button onClick={() => i18n.changeLanguage('de')}>Deutsch</button>
<button onClick={() => i18n.changeLanguage('en')}>English</button>
</>
)
}
```- Magic strings are hard-coded string values used directly in code.
- They often appear multiple times and are not given a descriptive name.
- Example: using
"welcome_message"directly in many places.
- No compile time checks: Typos in strings are not caught until runtime.
- Error-prone: Typos within the magic string are hard to catch and can break functionality.
- Hard to maintain: Changing a value requires updating every occurrence.
- Difficult to refactor: Refactoring tools may not catch all usages.
- No context: The meaning of the string is unclear without documentation.
title: Disadvantages of Simple JSON Key-Value Translations layout: image-right background: /json-translations.png level: 2
- No structure: Flat key-value pairs become hard to manage in large projects.
- Context issues: Same key may have different meanings in different places.
- No metadata: Can't add comments or notes for translators.
- Hard to organize: Grouping related translations is not straightforward.
- No validation: Typos or missing keys are only found at runtime.
- Size issues: Large JSON files can become unwieldy and slow to load.
- Magic strings:
- No compile-time checks; typos only found at runtime.
- Difficult to refactor and understand meaning.
- Standard JSON Key-Value pairs
- Lack of structure
- No meta data/context, comments or hints for developers or translators, plurals, or gender rules.
- Large files can be slow and unwieldy.
- Use your programming language's features (avoid magic strings)
- Use Typescript
- Use Interfaces
- Structure the JSON file and add metadata for the keys
- Split the JSON file into multiple files
- Use a naming convention for the keys, maybe some kind of nesting
- Extend value not to be a simple string, but an object with metadata
- Use tooling to support your localization process
- See missing translations
- Support creating new translations
- Support editing translations
- Use Typescript to avoid magic strings ➡️ easy
- Use Interfaces to define the structure of your translations
- introduce a new translation method/hook
/**
* Hook to return a function, which translates the given text identified by its key
* or returns the key itself, when no text is found.
*/
export function useTranslation(): Translate {
const intl = useIntl();
return (textKey: keyof I18nTexts, paramsObj?: Record<string, PrimitiveType>) => {
return translateText(intl, textKey, paramsObj);
};
}
/**
* Result function type allowing to translate a single text in functional components.
*/
export type Translate =
(textKey: keyof I18nTexts, paramsObj?: Record<string, PrimitiveType> | undefined)
=> string;Compose them:
import I18nTextsCommon from './I18nTextsCommon';
import I18nTextsModule from './I18nTextsModule';
/**
* A single interface containing keys for all translatable texts in the application.
*/
export default interface I18nTexts extends I18nTextsCommon, I18nTextsModule {}/**
* A single interface containing keys for all translatable texts in the application.
*/
export default interface I18nTexts {
app_title: WebExtensionMessage;
loading: WebExtensionMessage;
error_message_generic: WebExtensionMessage;
error_message: WebExtensionMessage;
debug_active: WebExtensionMessage;
debug_hint: WebExtensionMessage;
debug_mode: WebExtensionMessage;
user: WebExtensionMessage;
firstname: WebExtensionMessage;
lastname: WebExtensionMessage;
route_not_found: WebExtensionMessage;
}title: How to do all that? WebExtensionMessage layout: image-right background: /webextension-message.png level: 2
- this is the interface for a single translation entry
- its an standardized structure defined by the WebExtension API of Mozilla
export interface WebExtensionMessage {
message: string;
description?: string;
}In my case simplified, I omitted the placeholders part, which is not needed because that is supported by react-intl.
{
"app_title": {
"message": "Crazy App",
"description": "The title of the application, DOT NOT TRANSLATE!"
},
"loading": {
"message": "Lade ..."
},
"error_message_generic": {
"message": "Es ist ein Fehler ist aufgetreten. Bitte melden Sie den Fehler.",
"description": "Eine Beschreibung zur Message, die dem Übersetzer oder anderen Entwicklern helfen/Hinweise geben kann."
},
"error_message": {
"message": "Fehler: {error_message}",
},
"app_titleNew": {
"message": "hallo new"
},
"debug_active": {
"message": "Debug Modus aktiv. Zum deaktiveren die Tastenkombination **Ctrl + Alt + Shift + D** drücken.",
"description": "Dieser Text wird als Markdown interpretiert"
},
"debug_hint": {
"message": "Der Debug Modus kann durch die Tastenkombination **Ctrl + Alt + Shift + D** aktiviert werden.",
"description": "Dieser Text wird als Markdown interpretiert"
},
"debug_mode": {
"message": "Debug Modus"
},
"firstname": {
"message": "Vorname"
},
"lastname": {
"message": "Nachname"
},
"user": {
"message": "Benutzer"
},
"route_not_found": {
"message": "Route nicht gefunden"
}
}- usual i18n libraries expect a simple key-value pairs ➡️ map the
WebExtensionMessageto a simple string. - this is also the right place to add additional logic ➡️ returning the key itself if no translation is found and logging a warning in development mode.
- 💡 you may also return the key itself when running in a testing environment ➡️ in this case your test need not to be aware of the translations.
export default function translateText(
intl: IntlShape,
textKey: keyof I18nTexts,
paramsObj?: Record<string, PrimitiveType>
) {
try {
return intl.formatMessage({ id: textKey }, paramsObj);
} catch (error) {
// Do not log in test or production
if (process.env.NODE_ENV === 'development') {
console.warn(`Translation text key ${textKey} not found.`);
}
return textKey;
}
}VSCode extension: i18n Ally 
- Real-time translation key detection in your code
- Supports many frameworks (Vue, React, Angular, Svelte, etc.)
- Auto-completion and suggestions for translation keys
- Overlay for translation keys to see a translations instead of the key
- Inline editing of translation values directly in your editor
- Quick navigation to translation files and keys
- Missing & unused key detection for better translation coverage
- Visualization of translation status across languages
- Integrated with VSCode for a seamless workflow
Support the WebExtensionMessage format with our custom translate() function:
```yaml
# .vscode/i18n-ally-custom-framework.yml
```
```yaml
# .vscode/i18n-ally-custom-framework.yml
languageIds:
- javascript
- typescript
- javascriptreact
- typescriptreact
```
```yaml {all,8}
# .vscode/i18n-ally-custom-framework.yml
languageIds:
[...]
- typescriptreact
# Detect translate() and t() function calls
usageMatchRegex:
- "translate\\(['\"`](.*?)['\"`]"
- "[\\W]t\\(['\"`](.*?)['\"`]"
- "nameof<I18nConsts>\\(['\"`](.*?)['\"`]"
```
```yaml {all,12-16}
# .vscode/i18n-ally-custom-framework.yml
languageIds:
[...]
- typescriptreact
# Detect translate() and t() function calls
usageMatchRegex:
- "translate\\(['\"`](.*?)['\"`]"
- "[\\W]t\\(['\"`](.*?)['\"`]"
- "nameof<I18nConsts>\\(['\"`](.*?)['\"`]"
# Templates for refactoring
refactorTemplates:
- translate("$1")
- t("$1")
monopoly: false
```When there are more then 2 languages to be supported...
- a developer usually supports his native language and the development language, english
- developers don't like to "waste time" on translations
- ➡️ the translations should be done by translators
💡 it is a good idea to use a translation management system (TMS).
- Centralized translation management for all languages and projects
- Translation memory: reuse existing translations
- Collaboration between developers, translators and reviewers
- Glossary and consistency: maintain consistent terminology
- Quality assurance: built-in checks for missing or inconsistent translations
- Integration with development tools and CI/CD pipelines
- Support for context, plurals, and metadata
- Easy import/export of translation files in various formats
- Progress tracking and reporting for translation status
- Scalability: handle large projects with many languages and keys
- Git integration: version control for translation files
- 📌 Weblate
- 💡 Phrase
- Crowdin
- Lokalise
- POEditor
- Transifex
- Smartling
- Zanata
- Memsource
- SDL Trados
- Pontoon (Mozilla)
- Open Source: No vendor lock-in, self-hosting possible
- Tight Git integration: Syncs translations with your repos
- Real-time collaboration: Multiple contributors
- Translation memory and suggestions for consistency
- Quality checks: Automated validation for translations
- Supports many formats: JSON, PO, XLIFF, YAML etc.
- Custom workflows: Adapt to your team's process
- Extensible: Integrate with CI/CD and automation tools
- Active community and regular updates
- Self-hosted: Run Weblate on your own server (Linux, Docker, cloud VM etc.)
- Docker support: Official Docker images for easy deployment
- Managed hosting: Use Weblate's cloud service for hassle-free setup
- Create a new project and add your source language files
- Connect your Git repository for automatic translation sync
- Add and manage target languages for your project
- Use the web-based translation editor for easy translation
- Add additional information like screenshots
- Leverage translation memory with suggestions for consistency
- Manage glossary and terminology for quality and consistency
- Run automated quality checks and validations
- Assign roles and collaborate with team members (review, comment, approve)
- Use different workflows including reviewing and gateways
- Import/export translation files in various formats
- Track translation progress and statistics
title: Weblate Docker Architecture layout: image-right background: /weblate-logo.png backgroundSize: 95% level: 2
-
PostgreSQL Database (port 5432)
- Stores translations, projects, users
- Persistent data in
db_datavolume
-
Redis Cache (port 6379)
- Required for task queue & caching
- Data in
redis_datavolume
-
Weblate Application (port 8080)
- Web interface & API
- Data in
weblate_data&weblate_cachevolumes
WEBLATE_SITE_DOMAIN: localhost:8080WEBLATE_ADMIN_EMAIL: admin@example.comWEBLATE_ADMIN_PASSWORD: adminpasswordPOSTGRES_*&REDIS_*: Database connections


