Skip to content

Localization (i18n)

Translate user-facing strings (command descriptions, parameters, confirmation prompts) into different languages. This document covers configuration, file format, and validation.

What gets translated:

  • User command description, confirmation_text, and per-parameter descriptions.
  • Command group title and description (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.

DWE picks an active locale via the following precedence chain (highest to lowest):

  1. --lang flag — accepted by every dwe docs subcommand (show, list, search, export, generate, llms-txt). Per-invocation, not stored.
  2. language field in userconfigDWE_LANGUAGE overrides the value loaded from the user config file at this slot.
  3. System $LANG environment variable — parsed to a 2-letter code (ru_RU.UTF-8ru; C and POSIX are ignored).
  4. 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.

Terminal window
# Highest precedence: --lang flag on any docs subcommand
dwe docs show config/services/fields --lang ru
dwe docs list --lang ru
dwe docs generate --lang ru
# Via userconfig (~/.config/dwe/config or .dwe/config)
# language: de
dwe commands list
# → Picks German
# Via environment variable — overrides the userconfig `language` field
DWE_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 used
dwe commands list
# → Picks English

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 code

Missing workspace/i18n/ directory is silently treated as empty — the system falls back to English.

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 load

The strict validation catches typos early. Run dwe validate to surface any issues.

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.

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: "Производство"

Command groups (collections of related commands) can have translated titles and descriptions:

groups:
<groupId>: # e.g., "services.main.db"
title: "..." # group title
description: "..." # group description

Example:

workspace/i18n/ru.yml
groups:
services.main:
title: "Основные сервисы"
description: "Управление основными компонентами приложения"
services.tools:
title: "Инструменты"
description: "Утилиты и вспомогательные команды"

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"

A typical Russian translation file:

workspace/i18n/ru.yml
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: "Команды для управления основным приложением"

The dwe validate command checks translation files for common issues:

  1. Parse errors — strict YAML validation catches typos in field names (e.g., descripton: instead of description:). Fix the file and re-run dwe validate.

  2. 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.

  3. 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:

Terminal window
$ dwe validate
Diagnostics: 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.

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:

Terminal window
# Override the language for one command (env wins over userconfig and $LANG)
DWE_LANGUAGE=ru dwe commands list
# Persist for a shell session
export DWE_LANGUAGE=de
dwe commands list # → German
# Explicit --lang still wins over DWE_LANGUAGE
DWE_LANGUAGE=de dwe docs show config/services/fields --lang ru
# → Renders in Russian

Generate documentation in a specific language:

Terminal window
# Generate German documentation
dwe 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).

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.

Missing language files:

  • If workspace/i18n/ doesn’t exist, all translations fall back to English.
  • If workspace/i18n/ru.yml is missing but LANG=ru_RU.UTF-8, DWE falls back to English (silent; no warning).

Missing individual keys:

  • If a translation file has commands.deploy.description but no commands.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.

DWE separates localization into two distinct namespaces:

  1. Command/UI strings (this document): YAML files under workspace/i18n/<lang>.yml. Translations of command descriptions, parameters, UI buttons, and generated documentation section headers.
  2. Long-form markdown: Markdown files under docs/i18n/<lang>/reference/..., docs/i18n/<lang>/guides/..., and docs/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.

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.

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 to docs/ (e.g., reference/config/workspace, internals/architecture). Omit the .md extension.
  • <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...

How the hash is computed:

  1. The DWE build process (make build) walks docs/reference/, docs/guides/, and docs/internals/ (plus the repo-root README.md).
  2. For each markdown file, it computes sha256(file_bytes) and takes the first 12 hex characters.
  3. These hashes are embedded into the dwe binary as a manifest.
  4. 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 git knowledge.

Staleness check at runtime:

  1. 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*$).
  2. It compares the parsed hash against the embedded manifest.
  3. If they match → translation is current (no banner).
  4. If they differ → translation is outdated (info banner displayed).
  5. 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.
  • Missing translation: English version rendered with an info banner:
    ℹ Translation not available for `ru`. Showing English version.

To add or update a long-form markdown translation:

  1. Copy the English file:

    Terminal window
    cp docs/reference/config/services/index.md docs/i18n/ru/reference/config/services/index.md
    cp docs/reference/config/services/fields.md docs/i18n/ru/reference/config/services/fields.md
  2. Translate the content (keep the header):

    > Translated from: reference/config/services/index @ <english-hash>
    # Конфигурация сервисов
    ...
  3. 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
  4. Update the header:

    > Translated from: reference/config/services/index @ a1b2c3d4e5f6
  5. Create a pull request with both the source file and translated file. CI verifies that the header hash matches the embedded manifest.

When you run dwe docs show, dwe docs list, dwe docs export, or use the TUI, the active locale is determined by:

  1. --lang flag (docs subcommands only; e.g., dwe docs show config/services/fields --lang ru)
  2. DWE_LANGUAGE environment variable
  3. language setting in userconfig
  4. System $LANG (parsed to 2-letter code)
  5. 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.

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).

  • dwe docs show <topic> — Display documentation with automatic language fallback
  • dwe docs list — List available topics and languages
  • dwe docs export <dir> [--lang <code>] — Export docs with per-file fallback
  • dwe docs cache clear — Clear mermaid diagram cache
  • dwe docs — Open the interactive TUI browser
  • dwe commands list — displays command descriptions in the active locale
  • dwe commands <id> — shows translated command details and confirmation prompts
  • dwe docs generate --lang <code> — generate docs in a specific language
  • dwe validate — checks translation files (command/UI strings only; long-form docs validation is planned)