Localization (i18n)
Translate user-facing strings (command descriptions, parameters, confirmation prompts) into different languages. This document covers configuration, file format, and validation.
Contents
Section titled “Contents”- Overview
- Locale resolution
- File locations
- Config syntax
- Key reference
- Examples
- Validation
- Usage
- Behavior on missing translations
Overview
Section titled “Overview”What gets translated:
- User command
description,confirmation_text, and per-parameter descriptions. - Command group
titleanddescription(displayed in the TUI command browser). - Markdown generator section headers and property labels (
## Properties,## Parameters,**Working directory**, etc.).
What is NOT translated:
- DWE’s own Cobra command descriptions (
dwe deploy,dwe run,dwe docs, etc.) — these remain English in v1. This applies only to project user commands and UI strings. - Error messages and logs — these are machine-readable and always English.
- The long-form product documentation under
docs/reference/— separately scoped.
Key design principle: YAML command files stay 100% English (the canonical source); translations live in sidecar files under a known schema. Authors and translators don’t step on each other.
Locale resolution
Section titled “Locale resolution”DWE picks an active locale via the following precedence chain (highest to lowest):
--langflag — accepted by everydwe docssubcommand (show,list,search,export,generate,llms-txt). Per-invocation, not stored.languagefield in userconfig —DWE_LANGUAGEoverrides the value loaded from the user config file at this slot.- System
$LANGenvironment variable — parsed to a 2-letter code (ru_RU.UTF-8→ru;CandPOSIXare ignored). - Default:
en(English).
Codes like ru-RU, ru_RU, or ru_RU.UTF-8 normalize to ru at every layer. Empty inputs and C/POSIX are skipped, so the chain continues to the next source.
Examples
Section titled “Examples”# Highest precedence: --lang flag on any docs subcommanddwe docs show config/services/fields --lang rudwe docs list --lang rudwe docs generate --lang ru
# Via userconfig (~/.config/dwe/config or .dwe/config)# language: dedwe commands list# → Picks German
# Via environment variable — overrides the userconfig `language` fieldDWE_LANGUAGE=fr dwe commands list# → Picks French
# Via system $LANG (used only when no flag/config/env is set)LANG=es_ES.UTF-8 dwe commands list# → Picks Spanish
# When no locale is set, English is useddwe commands list# → Picks EnglishFile locations
Section titled “File locations”Translations live in project-level YAML files under the workspace/i18n/ directory. No global/user-level translation directory exists; translations are per-project.
Project translation location:
<project>/ workspace/ i18n/ ru.yml # Russian de.yml # German fr.yml # French # ... any 2-letter language codeMissing workspace/i18n/ directory is silently treated as empty — the system falls back to English.
Config syntax
Section titled “Config syntax”Translation files use YAML syntax with strict field validation — unknown keys cause a parse error (caught by dwe validate).
Valid file (ru.yml):
ui: docs.section.properties: "Свойства" docs.section.command: "Команда"
commands: build.docker: description: "Собрать образ Docker" confirmation_text: "Вы уверены?" params: tag: description: "Тег образа"
groups: build: title: "Сборка" description: "Команды сборки"Invalid file (typo in field name — strict decode catches it):
commands: build.docker: descripton: "..." # ← typo: "descripton" instead of "description" # → strict decode error on loadThe strict validation catches typos early. Run dwe validate to surface any issues.
Key reference
Section titled “Key reference”ui.* keys (UI strings)
Section titled “ui.* keys (UI strings)”These keys are populated by the English baseline shipped inside the dwe binary and can be overridden or extended in project files.
Keys in this namespace go under the ui: block in your YAML file — write the bare key name without the ui. prefix:
ui: docs.section.properties: "Propriétés" docs.property.id: "Identifiant"Section headers (markdown generator):
docs.section.properties→ “Properties”docs.section.command→ “Command”docs.section.parameters→ “Parameters”docs.section.context→ “Context”docs.section.environment→ “Environment Variables”docs.section.with→ “With”docs.section.script→ “Script”docs.section.argv→ “Argv”docs.section.files→ “Files”docs.section.steps→ “Steps”
Property labels (in property tables and inline descriptions):
docs.property.id→ “ID”docs.property.type→ “Type”docs.property.group→ “Group”docs.property.private→ “Private”docs.property.confirmation→ “Confirmation”docs.property.confirmation_text→ “Confirmation text”docs.property.success_message→ “Success message”docs.property.error_message→ “Error message”docs.property.shell→ “Shell”docs.property.service→ “Service”docs.property.workdir→ “Working directory”docs.property.builtin→ “Builtin”docs.property.compose_args→ “Compose args”docs.property.argv_append_from→ “Argv append from”docs.property.script→ “Script”
Workflow labels (Steps section rendering):
docs.workflow.parallel→ “parallel”docs.workflow.sub_steps→ “sub-steps”
TUI help modal (the ?-modal in the command and vars browser):
tui.help.title→ “Help”tui.help.section.navigation→ “Navigation”tui.help.section.panels→ “Panels”tui.help.section.actions→ “Actions”tui.help.section.general→ “General”tui.help.action.focus.next→ “Focus next panel”tui.help.action.focus.prev→ “Focus previous panel”tui.help.action.help→ “Toggle help”tui.help.action.quit→ “Quit”tui.help.action.nav.up/.down/.left/.right→ “Move up/down/left/right”tui.help.action.nav.top→ “Go to top”tui.help.action.nav.bottom→ “Go to bottom”tui.help.action.nav.page-up/.page-down→ “Page up/down”tui.help.action.filter→ “Filter”tui.help.action.inspect→ “Inspect”tui.help.action.cmd.skip-confirm→ “Skip confirmation”tui.help.action.cmd.force-form→ “Edit parameters”
The select action label is intentionally not keyed — it is mode-dependent (Select in the command browser, Edit in the vars browser) and resolves from the binding description rather than a translation key.
commands.<id>.* keys
Section titled “commands.<id>.* keys”Project-defined commands (under workspace/commands/) can have translations. The key structure mirrors the command ID and structure:
commands: <group>.<name>: # e.g., "services.main.db.migrate" description: "..." confirmation_text: "..." messages: success: "..." error: "..." params: <paramName>: # e.g., "force" description: "..." options: <value>: "..." # e.g., "prod": "Production"All fields are optional; omitted fields fall back to the English values in the command YAML file. Example:
# workspace/commands/tools/deploy.yml (English source)- id: deploy description: "Deploy the application" confirmation_text: "Deploy now?" messages: success: "Deployment completed successfully" error: "Deployment failed" params: - name: env description: "Environment (dev / prod)"
# workspace/i18n/ru.yml (Russian translation)commands: tools.deploy: description: "Развернуть приложение" # confirmation_text omitted → uses English "Deploy now?" messages: success: "Развертывание завершено успешно" error: "Развертывание не удалось" params: env: description: "Окружение (разработка / производство)" options: dev: "Разработка" prod: "Производство"groups.<id>.* keys
Section titled “groups.<id>.* keys”Command groups (collections of related commands) can have translated titles and descriptions:
groups: <groupId>: # e.g., "services.main.db" title: "..." # group title description: "..." # group descriptionExample:
groups: services.main: title: "Основные сервисы" description: "Управление основными компонентами приложения" services.tools: title: "Инструменты" description: "Утилиты и вспомогательные команды"Examples
Section titled “Examples”English baseline
Section titled “English baseline”The built-in English baseline is included in every DWE binary. You don’t need to create workspace/i18n/en.yml unless you want to override built-in UI strings:
# workspace/i18n/en.yml (optional; usually not needed)ui: docs.section.properties: "Properties"
commands: myapp.deploy: description: "Deploy the application"Russian translation
Section titled “Russian translation”A typical Russian translation file:
ui: docs.section.properties: "Свойства" docs.section.command: "Команда" docs.section.parameters: "Параметры" docs.section.context: "Контекст" docs.section.environment: "Переменные окружения" docs.section.with: "С" docs.section.script: "Скрипт" docs.section.argv: "Argv" docs.section.files: "Файлы" docs.property.id: "ID" docs.property.type: "Тип" docs.property.group: "Группа" docs.property.private: "Приватный" docs.property.confirmation: "Подтверждение" docs.property.confirmation_text: "Текст подтверждения" docs.property.success_message: "Сообщение об успехе" docs.property.error_message: "Сообщение об ошибке" docs.property.shell: "Shell" docs.property.service: "Сервис" docs.property.workdir: "Рабочая директория" docs.property.builtin: "Встроенная"
commands: myapp.deploy: description: "Развернуть приложение" confirmation_text: "Развернуть?" messages: success: "Приложение успешно развернуто" error: "Развертывание не удалось" params: env: description: "Окружение (dev/prod)" options: dev: "Разработка" prod: "Производство"
myapp.rollback: description: "Откатить развертывание"
groups: myapp: title: "Основное приложение" description: "Команды для управления основным приложением"Validation
Section titled “Validation”The dwe validate command checks translation files for common issues:
-
Parse errors — strict YAML validation catches typos in field names (e.g.,
descripton:instead ofdescription:). Fix the file and re-rundwe validate. -
Orphan entries — a translation references a command or group that no longer exists in
workspace/commands/. This is a warning (the translation is harmless but unused). Remove the orphan entry or rename it to match an existing command. -
Unknown UI keys — a
ui.*key that is not in the canonical whitelist. This is a warning. If the key is intentional (e.g., a custom UI string for a future feature), file a request in the project’s issue tracker to add it to the whitelist.
Example validation output:
$ dwe validateDiagnostics: 3 warnings, 0 errors
i18n.orphan [warning] workspace/i18n/ru.yml Translation references command "old.command" that no longer exists Hint: Rename or remove the entry
i18n.unknown_ui_key [warning] workspace/i18n/ru.yml Unknown ui key "ui.custom.label" Hint: Unknown ui key; if intentional, file a request to add it to the canonical set
No errors detected.All warnings are informational. To make them block CI, use dwe validate --strict.
Environment variables
Section titled “Environment variables”DWE_LANGUAGE is injected into the userconfig loader at startup; it replaces whatever language value lives in ~/.config/dwe/config or the project’s local config. It sits below an explicit --lang flag and above the system $LANG variable:
# Override the language for one command (env wins over userconfig and $LANG)DWE_LANGUAGE=ru dwe commands list
# Persist for a shell sessionexport DWE_LANGUAGE=dedwe commands list # → German
# Explicit --lang still wins over DWE_LANGUAGEDWE_LANGUAGE=de dwe docs show config/services/fields --lang ru# → Renders in Russiandocs generate —lang flag
Section titled “docs generate —lang flag”Generate documentation in a specific language:
# Generate German documentationdwe docs generate --lang de
# Documentation is written to docs/reference/commands/de/# English documentation is at docs/reference/commands/en/The --lang flag accepts any 2-letter language code. If the locale is not available in your project, DWE falls back to English. The generated documentation always lives under commands/<lang>/ (including English).
Scope and limitations
Section titled “Scope and limitations”User commands and generated command-reference documentation are localized via the YAML store described above. Long-form reference docs under docs/reference/ use a separate markdown-based namespace (see Long-form documentation translations below).
DWE’s own Cobra command-line descriptions (dwe deploy, dwe run, dwe docs, …) and runtime error messages remain English.
Behavior on missing translations
Section titled “Behavior on missing translations”Missing language files:
- If
workspace/i18n/doesn’t exist, all translations fall back to English. - If
workspace/i18n/ru.ymlis missing butLANG=ru_RU.UTF-8, DWE falls back to English (silent; no warning).
Missing individual keys:
- If a translation file has
commands.deploy.descriptionbut nocommands.deploy.confirmation_text, the confirmation text stays English. - If a
ui.*key is missing, the English fallback is used.
This graceful degradation ensures that partial translations always work correctly — translated and untranslated strings coexist in the same interface.
Long-form documentation translations
Section titled “Long-form documentation translations”DWE separates localization into two distinct namespaces:
- Command/UI strings (this document): YAML files under
workspace/i18n/<lang>.yml. Translations of command descriptions, parameters, UI buttons, and generated documentation section headers. - Long-form markdown: Markdown files under
docs/i18n/<lang>/reference/...,docs/i18n/<lang>/guides/..., anddocs/i18n/<lang>/internals/.... Translations of the built-in reference documentation, task-oriented guides, and architecture notes.
These namespaces use different loaders, different validators, and different file formats. They do NOT merge or share translations.
Structure and file layout
Section titled “Structure and file layout”Built-in (English):
docs/ reference/ # User-facing reference config/ workspace.md services/ index.md fields.md ... ... guides/ # Task-oriented recipes add-a-service.md daily-workflow.md ... internals/ # Architecture and developer docs packages.md architecture.md ...Translations:
docs/ i18n/ ru/ # Russian reference/ # Mirrors docs/reference/ config/ workspace.md # Russian translation services/ index.md fields.md guides/ # Mirrors docs/guides/ add-a-service.md internals/ # Mirrors docs/internals/ packages.md de/ # German reference/ config/ workspace.md internals/ ... fr/ # French (etc.) ...Each translated file is a standalone markdown file in the same location as its English counterpart, but under an i18n/<lang>/ directory. All three trees — reference/, guides/, and internals/ — are included in the staleness manifest and get the same runtime content-hash check.
Content-hash header format
Section titled “Content-hash header format”Translated markdown files include a header line that records when the translation was last synced with the English version:
# DWE Configuration
...Format:
> Translated from: <relative-path> @ <hash><relative-path>— Path to the English source file relative todocs/(e.g.,reference/config/workspace,internals/architecture). Omit the.mdextension.<hash>— First 12 characters of the SHA256 hash of the English source file’s content bytes.
Example of a properly formatted header:
> Translated from: reference/config/services/index @ 8f3e9d2c1a5b
# Services Configuration
This section describes how to configure services...Hash derivation and staleness check
Section titled “Hash derivation and staleness check”How the hash is computed:
- The DWE build process (
make build) walksdocs/reference/,docs/guides/, anddocs/internals/(plus the repo-rootREADME.md). - For each markdown file, it computes
sha256(file_bytes)and takes the first 12 hex characters. - These hashes are embedded into the
dwebinary as a manifest. - The manifest is committed to the repository so that every binary built from the same source sees the same hashes.
Why content-hash and not git commit SHA:
- Content-hash is stable across rebases, cherry-picks, and file renames. Git SHA changes with every rebase.
- Content-hash works on fresh tarball checkouts (no
.git/directory). Git SHA requires local git history. - Content-hash is transparent to translators: they can compute and paste the hash without
gitknowledge.
Staleness check at runtime:
- When you view a translated document via
dwe docs show,dwe docs export, or the TUI, DWE reads the translated file and parses the content-hash header (regex:^>\s*Translated from:\s*\S+\s*@\s*([0-9a-f]{12,64})\s*$). - It compares the parsed hash against the embedded manifest.
- If they match → translation is current (no banner).
- If they differ → translation is outdated (info banner displayed).
- If the manifest entry is missing or empty → check is disabled (no banner); this is a safety net for new files or an empty manifest on a fresh checkout.
User experience:
- Current translation: Rendered as-is, no banner.
- Stale translation: Rendered with a warning banner. The exact wording depends on the context (the actual content-hash values are not substituted into the banner):
- In the TUI browser:
⚠ This translation is outdated (last synced at previous version, current is newer). Press `e` to view the English version.
- In
dwe docs show/dwe docs export:⚠ Warning: This translation is outdated. Use `--lang en` to view the English version.
- In the TUI browser:
- Missing translation: English version rendered with an info banner:
ℹ Translation not available for `ru`. Showing English version.
Translator workflow
Section titled “Translator workflow”To add or update a long-form markdown translation:
-
Copy the English file:
Terminal window cp docs/reference/config/services/index.md docs/i18n/ru/reference/config/services/index.mdcp docs/reference/config/services/fields.md docs/i18n/ru/reference/config/services/fields.md -
Translate the content (keep the header):
> Translated from: reference/config/services/index @ <english-hash># Конфигурация сервисов... -
Get the English hash — the first 12 hex characters of
sha256(file_bytes):Terminal window sha256sum docs/reference/config/services/index.md | cut -c1-12# Output: a1b2c3d4e5f6 -
Update the header:
> Translated from: reference/config/services/index @ a1b2c3d4e5f6 -
Create a pull request with both the source file and translated file. CI verifies that the header hash matches the embedded manifest.
Locale resolution for long-form docs
Section titled “Locale resolution for long-form docs”When you run dwe docs show, dwe docs list, dwe docs export, or use the TUI, the active locale is determined by:
--langflag (docs subcommands only; e.g.,dwe docs show config/services/fields --lang ru)DWE_LANGUAGEenvironment variablelanguagesetting in userconfig- System
$LANG(parsed to 2-letter code) - Default:
en
Important: The docs locale resolution is unclamped. Command/UI strings use the clamped locale (from the YAML translation store), but long-form docs use the raw locale code to search the docs/i18n/<lang>/... tree. This allows different levels of translation completion in each namespace: you might have French translations of commands but only English long-form docs.
Validation
Section titled “Validation”The dwe validate command does NOT currently check long-form markdown translations (see Related commands below). Header format and hash mismatches are surfaced at runtime when you view the docs, not during validation.
A follow-up track (docs.* domain in the validate framework) is planned for v2 to surface stale translations during CI, but v1 keeps validation simple (command/UI strings only).
Related commands
Section titled “Related commands”dwe docs show <topic>— Display documentation with automatic language fallbackdwe docs list— List available topics and languagesdwe docs export <dir> [--lang <code>]— Export docs with per-file fallbackdwe docs cache clear— Clear mermaid diagram cachedwe docs— Open the interactive TUI browserdwe commands list— displays command descriptions in the active localedwe commands <id>— shows translated command details and confirmation promptsdwe docs generate --lang <code>— generate docs in a specific languagedwe validate— checks translation files (command/UI strings only; long-form docs validation is planned)