Documentation Localization: i18n Tools, Translation Workflows, and Locale Adaptation
Documentation localization is the process of adapting documentation for different languages and regions — going beyond translation to account for cultural context, formatting conventions, legal requirements, and technical infrastructure.
What You’ll Learn
- Internationalization (i18n) vs localization (l10n): what’s the difference
- Setting up Hugo/Docusaurus for multilingual documentation
- Translation management tools: Crowdin, Transifex, Lokalise
- Translation workflows: human translation, machine translation, hybrid
- Locale adaptation: dates, currencies, images, and cultural context
- Quality assurance for translated documentation
Why Localization Matters
Documentation in a user’s native language dramatically improves comprehension, adoption, and satisfaction. 72% of users spend most of their time on sites in their own language, and 55% only buy from websites with information in their language. For global products like Doda Browser and Durga Antivirus Pro, localized documentation is essential for reaching international markets — a single untranslated error message can break trust with users in any market.
Learning Path
flowchart LR
A[Content Strategy] --> B[Localization Guide<br/>You are here]
B --> C[Translation Workflows]
C --> D[Locale Adaptation]
D --> E[i18n Automation]
style B fill:#f90,color:#fff
i18n vs l10n
Internationalization (i18n)
The technical infrastructure that enables localization — making your site capable of supporting multiple languages:
- Content separation from templates
- Language detection and routing
- Right-to-left (RTL) support
- Unicode/UTF-8 encoding
- Locale-aware formatting
Localization (l10n)
The actual adaptation of content for specific locales:
Locale Code Structure: language_REGION
en-US — English (United States)
en-GB — English (United Kingdom)
fr-FR — French (France)
fr-CA — French (Canada)
ja-JP — Japanese (Japan)
zh-CN — Chinese (Simplified, China)Setting Up Multilingual Hugo
# hugo.yaml
defaultContentLanguage: en
languages:
en:
languageName: English
weight: 1
contentDir: content/en
params:
description: "Official documentation"
ja:
languageName: 日本語
weight: 2
contentDir: content/ja
params:
description: "公式ドキュメント"
fr:
languageName: Français
weight: 3
contentDir: content/fr
params:
description: "Documentation officielle"Content Directory Structure
content/
├── en/
│ ├── getting-started/
│ │ ├── _index.md
│ │ ├── installation.md
│ │ └── quickstart.md
│ └── api-reference/
│ └── _index.md
├── ja/
│ ├── getting-started/
│ │ ├── _index.md
│ │ └── installation.md
│ └── api-reference/
│ └── _index.md
└── fr/
└── getting-started/
└── _index.mdLanguage Switcher
<!-- partials/lang-switcher.html -->
{{ if .Site.IsMultiLingual }}
<nav class="lang-switcher">
{{ range .Site.Languages }}
<a href="{{ . | relLangURL }}" lang="{{ .Lang }}">
{{ .LanguageName }}
</a>
{{ end }}
</nav>
{{ end }}Translation Management Tools
| Tool | Best For | Pricing | Features |
|---|---|---|---|
| Crowdin | Open source, large projects | Free for open source | Git integration, TM, glossary |
| Transifex | Enterprise | Paid | AI translation, CMS integration |
| Lokalise | Small-medium teams | Paid | Screenshots, comments, QA |
| POEditor | Simple translation | Free tier | PO file editing, API |
| Weblate | Self-hosted | Free / paid | Git integration, consistent UI |
Crowdin Workflow
# crowdin.yml
project_id: "123456"
api_token: "your_token"
base_path: "content/en"
files:
- source: "/**/*.md"
translation: "/%two_letters_code%/**/%original_file_name%"
update_option: "update_as_unapproved"
languages_mapping:
two_letters_code:
zh-CN: zh-CN
pt-BR: pt-BRTranslation Workflows
Human Translation
Writer (English) → Review → Merge → Push to Crowdin
↓
Translators translate
↓
In-context review
↓
Proofreading
↓
Pull to repository
↓
CI build & deployMachine Translation + Human Review
# Auto-translate with DeepL API, then human review
curl -X POST 'https://api.deepl.com/v2/translate' \
--data 'auth_key=your_key' \
--data 'text=Hello world' \
--data 'target_lang=JA'
# Output: {"translations":[{"text":"こんにちは世界"}]}Translation Memory
Translation memory (TM) stores previously translated segments. When the same or similar text appears, it suggests the existing translation:
Source (English): "Click Save to continue"
TM Match: 100% → "クリック保存して続行"
Source (English): "Click Save to confirm"
TM Match: 80% → "クリック保存して確認" (needs review)Locale Adaptation Beyond Translation
Date and Time Formats
| Locale | Date Format | Example |
|---|---|---|
| en-US | M/D/YYYY | 3/15/2026 |
| en-GB | D/M/YYYY | 15/3/2026 |
| ja-JP | YYYY年M月D日 | 2026年3月15日 |
| de-DE | D.M.YYYY | 15.3.2026 |
Number and Currency
# en-US
"1,234.56" and "$1,234.56"
# de-DE
"1.234,56" and "1.234,56 €"
# ja-JP
"1,234.56" and "¥1,235"Cultural Adaptation
- Images and screenshots: Replace images with culturally appropriate ones (different skin tones, clothing, settings)
- Examples: Use locally relevant names and contexts (local cities, common names, familiar scenarios)
- Color meanings: Red means “danger” in Western cultures but “good luck” in Chinese culture
- Legal/compliance: Privacy policies, cookie notices, and terms must be adapted for local laws (GDPR for EU, CCPA for California)
Technical Adaptation
# Locale-aware date formatting
from datetime import datetime
import locale
def format_date_for_locale(date, locale_code):
# Set locale
locale.setlocale(locale.LC_TIME, locale_code)
# Format with locale-aware pattern
formats = {
'en_US': '%B %d, %Y',
'ja_JP': '%Y年%m月%d日',
'de_DE': '%d. %B %Y',
}
pattern = formats.get(locale_code, '%Y-%m-%d')
return date.strftime(pattern)
today = datetime(2026, 3, 15)
print(format_date_for_locale(today, 'en_US')) # March 15, 2026
print(format_date_for_locale(today, 'ja_JP')) # 2026年03月15日
print(format_date_for_locale(today, 'de_DE')) # 15. März 2026Quality Assurance
Translation QA Checklist
## Translation QA Checklist
### Accuracy
- [ ] Technical terms translated correctly
- [ ] Code examples not translated (code stays in English)
- [ ] Numbers, units, and measurements converted correctly
- [ ] Links and cross-references point to correct locale
### Completeness
- [ ] All source content is translated
- [ ] No untranslated strings remaining
- [ ] Images are localized or replaced
- [ ] Downloadable files are in the correct language
### Formatting
- [ ] Markdown formatting preserved
- [ ] Code blocks correctly formatted
- [ ] Tables maintain structure
- [ ] No broken layouts
### Cultural
- [ ] Examples are culturally appropriate
- [ ] No offensive or inappropriate content
- [ ] Locale-appropriate date/number formats
- [ ] Legal content adapted for local lawsAutomated QA
# Check for untranslated strings
grep -r '{{< translate >}}' content/ja/ | wc -l
# Counts how many strings still need translation
# Check for broken links in translated content
find content/ja -name "*.md" -exec markdown-link-check {} \;
# Verify all source files have translation files
for f in content/en/**/*.md; do
ja_file="${f/content\/en/content\/ja}"
if [ ! -f "$ja_file" ]; then
echo "Missing translation: $ja_file"
fi
doneCI/CD for Localization
# .github/workflows/localization.yml
name: Localization
on:
push:
branches: [main]
paths: ['content/en/**']
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Push to Crowdin
uses: crowdin/github-action@v1
with:
upload_sources: true
upload_translations: false
download_translations: true
crowdin_branch_name: main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
build-all:
needs: sync
strategy:
matrix:
lang: [en, ja, fr, de, zh-CN]
steps:
- uses: actions/checkout@v4
- name: Build ${{ matrix.lang }} site
run: |
hugo --config hugo.${{ matrix.lang }}.yaml \
--destination public/${{ matrix.lang }}Content Management for Localization
Source Content Guidelines
Write source (English) content with localization in mind:
- Use simple sentences: “Click Save” not “Upon clicking the Save button…”
- Avoid idioms: “Piece of cake” becomes “it’s easy to do”
- Be consistent: Use the same term for the same concept everywhere
- Leave placeholders:
{username}not “the user’s name” - Avoid culture-specific references: Sports analogies, local celebrities
Managing Translated Content
# translation_coverage.py
import os
class TranslationTracker:
def __init__(self, content_dir):
self.source_dir = f"{content_dir}/en"
self.locales = ["ja", "fr", "de", "zh-CN"]
def calculate_coverage(self):
source_files = self._get_files(self.source_dir)
total = len(source_files)
coverage = {}
for locale in self.locales:
locale_dir = f"{content_dir}/{locale}"
translated = 0
for sf in source_files:
lf = sf.replace("/en/", f"/{locale}/")
if os.path.exists(lf):
translated += 1
coverage[locale] = (translated / total) * 100
print(f"Source files: {total}")
for locale, pct in coverage.items():
print(f"{locale}: {pct:.0f}% translated")
tracker = TranslationTracker('content/')
tracker.calculate_coverage()Common Localization Mistakes
1. Translating Code
Code examples, variable names, and CLI commands should NOT be translated.
Fix: Keep code examples in English. Only translate surrounding explanations.
2. Ignoring Text Expansion
German text is ~30% longer than English. Japanese is ~10% shorter.
Fix: Design layouts with flexible containers. Test with each language.
3. No Context for Translators
Translating “Run” without knowing if it’s a button label or a verb creates errors.
Fix: Provide screenshots and context notes in the translation tool.
4. Inconsistent Terminology
A button labeled “Save” in one place and “Save Changes” in another confuses users.
Fix: Create a glossary of key terms. Require translators to use it.
5. Only Translating the Main Content
Navigation, error messages, alt text, and metadata are also user-facing.
Fix: Include all user-visible strings in the translation scope.
6. Not Updating Translations
When the English source changes, translations become outdated.
Fix: Automatically flag changed source content for retranslation.
7. Assuming All Locales Work Like English
Right-to-left (Arabic, Hebrew), character-based (Chinese, Japanese), and non-Latin scripts need special handling.
Fix: Test with each locale’s content. Verify layout, fonts, and input methods.
Practice Questions
1. What is the difference between i18n and l10n?
Internationalization (i18n) is the technical infrastructure for supporting multiple languages. Localization (l10n) is the actual translation and adaptation of content for specific locales.
2. What is translation memory and how does it help?
A database of previously translated segments. It suggests existing translations when the same or similar text appears, improving consistency and reducing translation cost.
3. Why should code examples not be translated?
Programming languages, commands, and variable names are language-agnostic. Translating them produces non-working code that misleads users.
4. What is text expansion and why does it matter for localization?
Translated text is often longer or shorter than the source. German expands ~30%; Japanese contracts ~10%. Layouts must accommodate these differences.
5. How do you handle RTL (right-to-left) languages in documentation?
Use CSS direction and text-align properties that adapt to the language attribute. Test layouts with Arabic and Hebrew content.
Challenge: Set up a multilingual Hugo site with English and Japanese. Translate 3 pages into Japanese using Crowdin or a manual workflow. Verify the translated site builds correctly with no missing content.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Internationalization Tools Guide | Deep dive into i18n tools and frameworks |
| Content Strategy for Docs | Planning content for global audiences |
| SEO for Technical Writing | International SEO and hreflang tags |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro