Skip to content

Commit 3909516

Browse files
authored
Add parity test and translator guidance for language files (#15265)
Keys added to _language.yml did not reach the per-language files, and nothing checked for it, so localized pages fell back to English for those strings (#14822). Two files also used keys that no longer match _language.yml. This adds a test that keeps the language files in step with _language.yml, and translator comments that say which sense of each key to use. - tests/unit/language-parity.test.ts: fails when a language file has a key missing from _language.yml, when a base language lacks a key that is not in language-known-gaps.yml, when that list goes stale, or when a value does not keep the {0} placeholders of the English value. Region files are overlays on their base language, so they skip only the missing-key check. - lt and sv: rename the stale keys (search to search-label, and callout-danger-title to callout-caution-title), keeping the existing values. The Swedish file (#5000) used a key name that was already gone when it was added. - _language.yml: translator comments above each key missing from some file: what the string is, where it shows, and which sense to use. - src/resources/language/README.md and AGENTS.md: the full list of language files and contributing guidance for translators, including disclosure of AI help. Related to #14822. Once #14203 lands, its Arabic file needs every key translated or listed in language-known-gaps.yml.
1 parent a31f8b0 commit 3909516

9 files changed

Lines changed: 331 additions & 29 deletions

File tree

‎.claude/rules/rendering/localization.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ paths:
77
# Localization
88

99
For how localized strings flow from `_language.yml` through metadata merging into Pandoc templates and the crossref/authors/callouts Lua filters, see `llm-docs/localization-architecture.md`.
10+
11+
For editing `_language-*.yml` files, follow `src/resources/language/README.md` and `src/resources/language/AGENTS.md` (translator notes, placeholders, parity test).

‎news/changelog-1.12.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ All changes included in 1.12:
2020
## Other fixes and improvements
2121

2222
- ([#14822](https://github.com/quarto-dev/quarto-cli/issues/14822)): Translate the `tools-share`, `tools-download`, and `skip-to-content` language keys in Greek. (author: @sogav21)
23+
- ([#14822](https://github.com/quarto-dev/quarto-cli/issues/14822)): The Lithuanian search label and the Swedish caution callout title no longer fall back to English.

‎src/resources/language/AGENTS.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Language files
2+
3+
Before editing any `_language-*.yml`:
4+
5+
- Read `README.md` in this directory.
6+
- Read the translator comment above each key you translate in `_language.yml`. Some strings are screen-reader labels or use words in a specific sense.
7+
- Keep the `{0}`-style placeholders exactly as they appear in the English value.
8+
- Reuse the vocabulary and verb forms already in the target file.
9+
- Region files (`de-CH`, `fr-CA`, `pt-BR`, `zh-TW`) are overlays: only add keys that differ from the base language.
10+
- Run `unit/language-parity.test.ts` (see `.claude/rules/testing/overview.md`); an English key you cannot translate goes in `tests/unit/language-known-gaps.yml`.

‎src/resources/language/README.md‎

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,44 @@ In some circumstances Quarto, Pandoc, and LaTeX will generate textual output tha
44

55
This directory includes a set of built-in translations, including:
66

7-
| File | Language |
8-
| --------------------- | ---------------------------- |
9-
| `_language.yml` | English (default) |
10-
| `_language-az.yml` | Azerbaijani |
11-
| `_language-eu.yml` | Basque |
12-
| `_language-ca.yml` | Catalan |
13-
| `_language-zh.yml` | Chinese |
14-
| `_language-cs.yml` | Czech |
15-
| `_language-nl.yml` | Dutch |
16-
| `_language-fi.yml` | Finnish |
17-
| `_language-fr.yml` | French |
18-
| `_language-de.yml` | German |
19-
| `_language-it.yml` | Italian |
20-
| `_language-he.yml` | Hebrew |
21-
| `_language-ja.yml` | Japanese |
22-
| `_language-kr.yml` | Korean |
23-
| `_language-pl.yml` | Polish |
24-
| `_language-pt.yml` | Portuguese |
25-
| `_language-ru.yml` | Russian |
26-
| `_language-es.yml` | Spanish |
27-
| `_language-tr.yml` | Turkish |
28-
| `_language-zh-TW.yml` | Traditional Chinese (Taiwan) |
29-
| `_language-ua.yml` | Ukrainian |
7+
| File | Language |
8+
| ----------------------- | -------------------------------------- |
9+
| `_language.yml` | English (default) |
10+
| `_language-az.yml` | Azerbaijani |
11+
| `_language-eu.yml` | Basque |
12+
| `_language-pt-BR.yml` | Brazilian Portuguese (overlay) |
13+
| `_language-bg.yml` | Bulgarian |
14+
| `_language-fr-CA.yml` | Canadian French (overlay) |
15+
| `_language-ca.yml` | Catalan |
16+
| `_language-zh.yml` | Chinese |
17+
| `_language-cs.yml` | Czech |
18+
| `_language-da.yml` | Danish |
19+
| `_language-nl.yml` | Dutch |
20+
| `_language-fi.yml` | Finnish |
21+
| `_language-fr.yml` | French |
22+
| `_language-de.yml` | German |
23+
| `_language-el.yml` | Greek |
24+
| `_language-he.yml` | Hebrew |
25+
| `_language-is.yml` | Icelandic |
26+
| `_language-id.yml` | Indonesian |
27+
| `_language-it.yml` | Italian |
28+
| `_language-ja.yml` | Japanese |
29+
| `_language-ko.yml` | Korean |
30+
| `_language-lt.yml` | Lithuanian |
31+
| `_language-nb.yml` | Norwegian Bokmål |
32+
| `_language-nn.yml` | Norwegian Nynorsk |
33+
| `_language-pl.yml` | Polish |
34+
| `_language-pt.yml` | Portuguese |
35+
| `_language-ru.yml` | Russian |
36+
| `_language-sr-Latn.yml` | Serbian (Latin script) |
37+
| `_language-sk.yml` | Slovak |
38+
| `_language-sl.yml` | Slovenian |
39+
| `_language-es.yml` | Spanish |
40+
| `_language-sv.yml` | Swedish |
41+
| `_language-de-CH.yml` | Swiss German (overlay) |
42+
| `_language-zh-TW.yml` | Traditional Chinese (Taiwan) (overlay) |
43+
| `_language-tr.yml` | Turkish |
44+
| `_language-ua.yml` | Ukrainian |
3045

3146
The use of these translations is triggered by the [`lang`](https://pandoc.org/MANUAL.html#language-variables) Pandoc metadata variable, which identifies the main language of the document using IETF language tags (following the [BCP 47](https://tools.ietf.org/html/bcp47) standard), such as `en` or `en-GB`. The [Language subtag lookup](https://r12a.github.io/app-subtags/) tool can look up or verify these tags.
3247

@@ -41,10 +56,20 @@ lang: fr
4156

4257
## Contributing Localizations
4358

44-
We welcome contributions of additional languages! To contribute a localization:
59+
We welcome translations for new languages and for missing strings in existing languages! To contribute a localization:
4560

46-
1. Make a copy of the `_language.yml` file with the appropriate IETF language tag appended to the filename (e.g. `_language-fr.yml`).
61+
1. For a new language, make a copy of the `_language.yml` file with the appropriate IETF language tag appended to the filename (e.g. `_language-fr.yml`).
4762

48-
2. Translate the English strings therein to the target language.
63+
2. Translate the English strings into the target language. Before translating a key, read its translator comment in `_language.yml`. Some strings are screen-reader labels or use words in a specific sense, as the comments explain.
4964

50-
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests) with your new language translation.
65+
3. Reuse the vocabulary and verb forms already used in your language file so the interface stays consistent.
66+
67+
4. Keep the `{0}`-style placeholders exactly as they appear in the English value.
68+
69+
5. Submit a [pull request](https://help.github.com/articles/using-pull-requests) with your translation.
70+
71+
A few more things to know:
72+
73+
- **Region files are overlays.** `de-CH`, `fr-CA`, `pt-BR` and `zh-TW` are read on top of `de`, `fr`, `pt` and `zh`, so they only need the keys that differ from their base language.
74+
- **AI help is welcome.** Check the translation against the translator comments and existing strings in the language file, and mention in the pull request that you used AI.
75+
- **Checking your file.** `tests/unit/language-parity.test.ts` fails if a language file has a key that is not in `_language.yml`, misses a key, or changes the placeholders. If you add an English key that you cannot translate, add its language entries to `tests/unit/language-known-gaps.yml` instead.

‎src/resources/language/_language-lt.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ search-clear-button-title: "Išvalyti"
7171
search-text-placeholder: ""
7272
search-detached-cancel-button-title: "Atšaukti"
7373
search-submit-button-title: "Pateikti"
74-
search: "Paieška"
74+
search-label: "Paieška"
7575

7676
toggle-section: "Rodyti/Slėpti skiltį"
7777
toggle-sidebar: "Rodyti/Slėpti šoninę navigacijos skiltį"

‎src/resources/language/_language-sv.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ callout-tip-title: "Tips"
3939
callout-note-title: "Notera"
4040
callout-warning-title: "Varning"
4141
callout-important-title: "Viktigt"
42-
callout-danger-title: "Försiktigt"
42+
callout-caution-title: "Försiktigt"
4343

4444
code-summary: "Kod"
4545

‎src/resources/language/_language.yml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ related-formats-title: "Other Formats"
55
related-notebooks-title: "Notebooks"
66
source-notebooks-prefix: "Source"
77
other-links-title: "Other Links"
8+
# Heading above the list of hyperlinks to the document's code (repository, Binder, Dev Container);
9+
# plural noun phrase, parallel to other-links-title.
810
code-links-title: "Code Links"
11+
# Link text that opens the repository in a GitHub Codespace; "Launch" = start/open, not publish.
12+
# "Dev Container" is a product name, usually left untranslated; use the same verb as launch-binder-title.
913
launch-dev-container-title: "Launch Dev Container"
14+
# "Binder" is the mybinder.org service (interactive Jupyter environment); keep the name untranslated.
15+
# Use the same verb as launch-dev-container-title.
1016
launch-binder-title: "Launch Binder"
1117

1218
article-notebook-label: "Article Notebook"
@@ -32,13 +38,16 @@ title-block-author-plural: "Authors"
3238
title-block-affiliation-single: "Affiliation"
3339
title-block-affiliation-plural: "Affiliations"
3440
title-block-published: "Published"
41+
# Label shown above the document's last-modified date (date-modified) in the title block.
42+
# Use the same form as title-block-published (e.g. "Date of modification" if that one is "Date of publication").
3543
title-block-modified: "Modified"
3644
title-block-keywords: "Keywords"
3745

3846
callout-tip-title: "Tip"
3947
callout-note-title: "Note"
4048
callout-warning-title: "Warning"
4149
callout-important-title: "Important"
50+
# Callout box title meaning "take care"; keep distinct from callout-warning-title.
4251
callout-caution-title: "Caution"
4352

4453
code-summary: "Code"
@@ -49,7 +58,11 @@ code-tools-hide-all-code: "Hide All Code"
4958
code-tools-view-source: "View Source"
5059
code-tools-source-code: "Source Code"
5160

61+
# Sidebar menu in books grouping social-media links for the current page;
62+
# "Share" = post a link to the page, not a portion. Pairs with tools-download.
5263
tools-share: "Share"
64+
# Book toolbar: label for the downloads menu and prefix before each format name
65+
# (e.g. "Download PDF"). Choose wording that reads naturally before a format name.
5366
tools-download: "Download"
5467

5568
code-line: "Line"
@@ -64,6 +77,9 @@ repo-action-links-issue: "Report an issue"
6477

6578
back-to-top: "Back to top"
6679

80+
# Accessibility skip link: first link on the page, shown only on keyboard focus;
81+
# it jumps past the header and navigation to the main content. Use the usual web
82+
# skip-link phrasing ("go to main content"), not "omit".
6783
skip-to-content: "Skip to main content"
6884

6985
search-no-results-text: "No results"
@@ -76,6 +92,8 @@ search-clear-button-title: "Clear"
7692
search-text-placeholder: ""
7793
search-detached-cancel-button-title: "Cancel"
7894
search-submit-button-title: "Submit"
95+
# Accessible name and tooltip for the navbar button that opens site search.
96+
# Use the conventional one-word label for a website search box.
7997
search-label: "Search"
8098

8199
toggle-section: "Toggle section"
@@ -102,12 +120,17 @@ scrollable-code-label: "Scrollable code"
102120
scrollable-output-label: "Scrollable output"
103121

104122
# Announced by screen readers after a link that opens in a new tab (never shown as visible text).
123+
# Describes the link (third person, "opens"), not a command; keep the parentheses.
124+
# "Tab" is a browser tab, not a window or a tab control.
105125
link-external-newwindow-label: "(opens in a new tab)"
106126

107127
# Accessible name for the button that dismisses a website announcement (never shown as visible text).
128+
# "Dismiss" = close/hide the banner (the button shows only an X icon), not reject or delete.
129+
# Imperative, like other button labels; use the language's usual "close" verb.
108130
announcement-dismiss-label: "Dismiss announcement"
109131

110132
# Accessible name for the Reveal.js slide menu button (never shown as visible text).
133+
# "Slide" is a presentation slide (PowerPoint sense); use a noun phrase, not a command.
111134
revealjs-menu-button-label: "Slide menu"
112135

113136
crossref-fig-title: "Figure"
@@ -147,11 +170,15 @@ listing-page-field-filename: "File Name"
147170
listing-page-field-filemodified: "Modified"
148171
listing-page-field-subtitle: "Subtitle"
149172
listing-page-field-readingtime: "Reading Time"
173+
# Label for the number of words in each listed document (a quantity, not a counter tool).
174+
# Shown with other listing fields such as "Reading Time"; keep it short.
150175
listing-page-field-wordcount: "Word Count"
151176
listing-page-field-categories: "Categories"
152177
listing-page-minutes-compact: "{0} min"
153178
listing-page-category-all: "All"
154179
listing-page-no-matches: "No matching items"
180+
# Word count shown per item in a listing, e.g. "1,234 words"; {0} is the formatted number.
181+
# A single string serves every count; pick the plural form that reads best for large numbers.
155182
listing-page-words: "{0} words"
156183
listing-page-filter: "Filter"
157184

‎tests/unit/language-known-gaps.yml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# <key>: [<lang>, ...] languages whose translation of <key> is still missing (languages sorted)
2+
announcement-dismiss-label: [az, bg, ca, cs, da, de, el, es, eu, fi, fr, he, id, is, it, ja, ko, lt, nb, nl, nn, pl, pt, ru, sk, sl, sr-Latn, sv, tr, ua, zh]
3+
code-links-title: [sr-Latn]
4+
launch-binder-title: [sr-Latn]
5+
launch-dev-container-title: [sr-Latn]
6+
link-external-newwindow-label: [az, bg, ca, cs, da, de, es, eu, fi, fr, he, id, is, it, ja, ko, lt, nb, nl, nn, pl, pt, ru, sk, sl, sr-Latn, sv, tr, ua, zh]
7+
listing-page-field-wordcount: [sr-Latn]
8+
listing-page-words: [sr-Latn]
9+
revealjs-menu-button-label: [az, bg, ca, cs, da, de, es, eu, fi, he, id, is, it, ja, ko, lt, nb, nl, nn, pl, pt, ru, sk, sl, sr-Latn, sv, tr, ua, zh]
10+
skip-to-content: [bg, ca, cs, da, eu, fi, he, id, is, ko, lt, nb, nn, pl, ru, sk, sl, sr-Latn, sv, tr, ua]
11+
title-block-modified: [ko]
12+
tools-download: [ca, cs, da, de, fi, fr, he, id, it, ja, ko, lt, nb, nl, nn, pl, pt, ru, sk, sr-Latn, sv, tr, ua, zh]
13+
tools-share: [ca, cs, da, de, fi, fr, he, id, it, ja, ko, lt, nb, nl, nn, pl, pt, ru, sk, sr-Latn, sv, tr, ua, zh]

0 commit comments

Comments
 (0)