Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
90 changes: 90 additions & 0 deletions docs/TRANSLATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Translated documentation

The docs site is multilingual. English is the source of truth; every other
language is a translation of an English page and is regenerated from it.

## How translations are stored

Translations live **beside** their English page using Hugo's filename-suffix
layout:

```
content/get_started/about/about_defectdojo.md <- English (source)
content/get_started/about/about_defectdojo.de.md <- German
```

We deliberately do not use a per-language `contentDir`, because that would
require moving all 466 English pages into `content/en/` and would break
existing tooling and any open docs PR.

A translated page keeps the English page's `slug`, `weight`, `aliases`, and
`audience` values, so URLs, ordering, and the Open Source / Pro version
toggle behave identically in every language. Only `title`, `description`,
`summary`, and the body text are translated.

## What is translated

The core guides: `get_started`, `import_data`, `triage_findings`,
`asset_modelling`, `metrics_reports`, `issue_tracking`, `admin`, `automation`,
`help`, `navigation`, and the site home page. 160 pages, about 102,000 words.

Deliberately **not** translated:

- `releases/` changelogs - they change with every release, and stale
translations of a changelog are worse than an English one.
- `supported_tools/` parser reference - 219 pages that are mostly tables,
scanner names, and CWE/CVE identifiers, which are the same in any language.
- `archived_docs/`.

Navigation entries for untranslated sections point at the English URL, so
nothing in the nav 404s in a translated language.

## Site chrome

UI text that lives in templates rather than content (navigation labels,
homepage cards, footer, screen-reader labels) is looked up with Hugo's
`i18n` function and translated in `i18n/<lang>.toml`. English values are in
`i18n/en.toml`; adding a key there without adding it to every language is
safe (missing keys fall back to English).

Terminology comes from the product UI catalogs (`dojo/locale/<lang>/`), so a
severity or status word reads the same in the docs as it does in DefectDojo.

## Adding a language

Languages are added to `config/_default/languages.toml` **only once their
translated content exists** - the switcher must never offer a language with
nothing behind it. Per language you need:

1. `content/**/<page>.<lang>.md` for the translated pages.
2. `i18n/<lang>.toml` for the chrome strings.
3. `config/_default/menus/menus.<lang>.toml` - translated labels, same URLs,
prefixed with `/<lang>` for sections that exist in that language.
4. The `[<lang>]` block in `languages.toml`, plus
`languageDirection = "rtl"` for Arabic, Hebrew, Persian, and Urdu.

The pipeline that produces 1-3 is the `i18n-translate` skill: it exports
pages to JSON, fans translation out to per-language agents, and applies the
results only after checking that code fences, shortcodes, link URLs, heading
structure, and protected frontmatter survived unchanged.

## Keeping translations current

**Refresh quarterly.** English pages change constantly; a translation that
silently rots tells non-English readers we do not care about them. The
refresh is incremental - export only the pages whose English source changed
since the last run:

```bash
export_docs.py --content content --work <work> --langs de \
--changed-since <git-ref-of-last-refresh>
```

Everything downstream is unchanged, because pages are addressed by path.

## Provenance

These translations are machine-generated and integrity-checked (structure,
tokens, and terminology are verified automatically), but they have **not**
been reviewed by native speakers. Treat a native-speaker pass as the next
step for any language you intend to promote heavily.
2 changes: 1 addition & 1 deletion docs/config/_default/hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ summarylength = 20 # 70 (default)

# Multilingual
defaultContentLanguage = "en"
disableLanguages = ["de", "nl"]
disableLanguages = []
defaultContentLanguageInSubdir = false

copyRight = "Copyright (c) 2020-2025 DefectDojo, Inc."
Expand Down
40 changes: 38 additions & 2 deletions docs/config/_default/languages.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
# Languages are added here only once their translated content exists, so the
# switcher never offers a language with nothing behind it. Translations live
# beside the English page as <page>.<lang>.md (Hugo filename-suffix layout);
# per-language contentDir would require moving all English content into
# content/en and would break existing tooling and open PRs.
#
# Right-to-left languages (ar, he, fa, ur) need languageDirection = "rtl";
# layouts/baseof.html emits <html dir> from it.

[en]
languageName = "English"
contentDir = "content/en"
weight = 10
[en.params]
languageISO = "EN"
languageTag = "en-US"
languageTag = "en-US"

[de]
languageName = "Deutsch"
weight = 20
[de.params]
languageISO = "DE"
languageTag = "de"

[es]
languageName = "Español"
weight = 30
[es.params]
languageISO = "ES"
languageTag = "es"

[fr]
languageName = "Français"
weight = 40
[fr.params]
languageISO = "FR"
languageTag = "fr"

[ja]
languageName = "日本語"
weight = 50
[ja.params]
languageISO = "JA"
languageTag = "ja"
131 changes: 131 additions & 0 deletions docs/config/_default/menus/menus.de.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Generated: labels translated; URLs point at the translated page when
# that section exists in this language, and fall back to the English
# URL when it does not (e.g. supported_tools), so no nav item 404s.
# Social entries are copied verbatim so the icons survive.
# Keep the entry set in step with menus.en.toml: Hugo does not merge menus
# across languages, so a tab missing here is a tab missing in German, and a
# URL that only existed in an older English menu is a 404 the link checker
# will catch.
[[main]]
name = "Erste Schritte"
url = "/de/get_started/about/about_defectdojo"
weight = 10

[[main]]
name = "Daten importieren"
url = "/de/import_data/import_intro/comparison/"
weight = 12

[[main]]
name = "Befunde triagieren"
url = "/de/triage_findings/findings_workflows/intro_to_findings/"
weight = 12

[[main]]
name = "Assets modellieren"
url = "/de/asset_modelling/engagements_tests/os__assets/"
weight = 13

[[main]]
name = "Connectors"
url = "/de/connectors/about/"
weight = 13

[[main]]
name = "Metriken & Berichte"
url = "/de/metrics_reports/dashboards/introduction_dashboard/"
weight = 14

[[main]]
name = "Sensei"
# Sensei is Pro-only. Default to the Open Source "Pro feature" page; custom.js
# swaps this tab to the full guide (/sensei/about_sensei/) when Pro is selected.
url = "/de/sensei/os__sensei/"
weight = 14

[[main]]
name = "Administration"
url = "/de/admin/admin_intro/intro/"
weight = 16

[[main]]
name = "Automatisierung"
url = "/de/automation/api/api-v2-docs/"
weight = 15

[[main]]
name = "Unterstützte Tools"
url = "/supported_tools/"
weight = 16

# Left-hand section sidebar for the Sensei chapter. pageRef resolves per
# language, so these point at the German pages automatically.
[[sidebar_sensei]]
name = "Sensei"
pageRef = "/sensei/OS__sensei"
weight = 0

[[sidebar_sensei]]
name = "Über Sensei"
pageRef = "/sensei/about_sensei"
weight = 1

[[sidebar_sensei]]
name = "Sensei einrichten"
pageRef = "/sensei/setup_sensei"
weight = 2

[[sidebar_sensei]]
name = "Befunde mit Sensei beheben"
pageRef = "/sensei/fixing_findings"
weight = 3

[[sidebar_sensei]]
name = "Sensei-Referenz"
pageRef = "/sensei/sensei_reference"
weight = 4

[[social]]
name = "YouTube"
pre = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-youtube" viewBox="0 0 16 16"><path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.01 2.01 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.01 2.01 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31 31 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.01 2.01 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A100 100 0 0 1 7.858 2zM6.4 5.209v4.818l4.157-2.408z"/></svg>'
url = "https://www.youtube.com/@defectdojo"
weight = 9

[[social]]
name = "X"
pre = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 4l11.733 16h4.267l-11.733 -16z"></path><path d="M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772"></path></svg>'
url = "https://x.com/defectdojo"
weight = 10

[[social]]
name = 'Linkedin'
pre = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16"><path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/></svg>'
url = "https://www.linkedin.com/company/defectdojo/"
weight = 10

[[social]]
name = "GitHub"
pre = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"></path></svg>'
url = "https://github.com/DefectDojo/django-DefectDojo"
post = "v0.1.0"
weight = 30

[[footer]]
name = "DefectDojo.com"
url = "https://defectdojo.com"
weight = 10

[[footer]]
name = "GitHub"
url = "https://github.com/DefectDojo/django-DefectDojo"
weight = 20

[[footer]]
name = "Community"
url = "https://defectdojo.com/open-source"
weight = 30

[[footer]]
name = "Support"
url = "mailto:support@defectdojo.com"
weight = 40
Loading
Loading