Interface language
Choose Menu > Edit > Preferences… (Command-comma on macOS, Ctrl-comma elsewhere) > Interface language and select Auto, English, 日本語, 简体中文, 繁體中文, Čeština, Português (Brasil), Deutsch, Español, Français, Русский, Қазақша, Български, తెలుగు, Magyar, Українська, Italiano, العربية, کوردیی ناوەندی, Polski, Slovenščina, Ελληνικά, Türkçe, Lietuvių, Tiếng Việt, Suomi, Shqip, Català, Română or Norsk bokmål. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged.
Displayed keyboard shortcuts use the same active interface language.
For example, German shows Strg+K and
Strg+Umschalt+S in the toolbar, menus, command palette,
tooltips and Keyboard shortcuts dialog; macOS keeps its modifier symbols
(⌘, ⇧, ⌃). Key names have their
own key catalog context, so the Delete key can read
Entf while the command remains Löschen. A
missing key-name entry falls back to the English key name. Actual
bindings and machine-facing command ids remain stable.
Auto (the default) follows the system language:
LC_ALL, LC_MESSAGES or LANG, then
the preferred-languages list on macOS. Any Portuguese locale
(pt_BR, pt_PT) uses the Brazilian catalog, any
French locale (fr, fr_FR, fr_CA,
fr_BE) the French one, any German locale (de,
de_DE, de_AT, de_CH) the German
one, and Chinese locales for Taiwan, Hong Kong and Macau
(zh_TW, zh_HK, zh_MO,
zh-Hant) the Traditional Chinese one; other Chinese locales
(zh, zh_CN, zh_SG,
zh-Hans) the Simplified Chinese one. Telugu locales
(te, te_IN) use the Telugu catalog, Bulgarian
locales (bg, bg_BG) the Bulgarian one, and
Hungarian locales (hu, hu_HU) the Hungarian
one, and Ukrainian locales (uk, uk_UA) the
Ukrainian one, and Italian locales (it, it_IT,
it_CH) the Italian one, and Arabic locales
(ar, ar_MA, ar_EG,
ar_SA, …) the Arabic one, and Turkish locales
(tr, tr_TR) the Turkish one, and Slovenian
locales (sl, sl_SI) the Slovenian one,
Vietnamese locales (vi, vi_VN) the Vietnamese
one, Albanian locales (sq, sq_AL,
sq_XK, …) the Albanian one, Catalan locales
(ca, ca_ES, ca_AD,
ca_ES@valencia, …) the Catalan one, and Romanian locales
(ro, ro_RO, ro-RO,
ro_MD) the Romanian one. Norwegian Bokmål locales
(nb, nb_NO) use the Bokmål catalog, and so do
general Norwegian (no, no_NO) and Nynorsk
(nn, nn_NO), which have no catalog of their
own. A system language without a catalog shows English. On Windows,
Auto uses the Windows display languages, in preference
order, when no supported locale is selected by those environment
variables. Neutral C/POSIX locale values
(including C.UTF-8) inherited from a launcher do not
override the Windows display language; explicitly named supported
locales such as fr_FR.UTF-8 still do. The query asks
Windows directly (GetUserPreferredUILanguages, through the
sys-locale crate) without starting a process, and is cached
for the process. If detection fails or the display language has no
catalog, Codex shows English.
The control channel exposes the setting through
ui.set:
{"method":"ui.set","params":{"key":"language","value":"ja"}}
The value is auto or a language code (en,
ja, zh-hans, zh-hant,
cs, pt-br, de, es,
fr, ru, kk, bg,
te, hu, uk, it,
ar, ckb, pl, sl,
el, tr, lt, vi,
fi, sq, ca, ro or
nb), in any case. ui.state reports
language as written in the preferences (auto,
en, ja, zh-hans,
zh-hant, cs, pt-br,
de, es, fr, ru,
kk, bg, te, hu,
uk, it, ar, ckb,
pl, sl, el, tr,
lt, vi, fi, sq,
ca, ro or nb). Unknown values
return an error without changing the current setting. Preferences saved
before this setting existed follow the system language.
Dialogs, panels, menus, notices and history labels go through the
catalog (tl!). Most shipped catalogs (Japanese, Chinese,
Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian,
Ukrainian, Italian, Brazilian Portuguese, Arabic, Kurdish, Slovenian,
Kazakh, Polish, Greek, Turkish, Lithuanian, Vietnamese, Romanian,
Norwegian Bokmål and others) cover the current localizable surface
(about 2,000–2,700 entries each), and Czech (2,674 entries) covers the
same surface. Untranslated labels use English. Error details that come
from the engine or the operating system are shown as they are, inside a
translated frame ("操作「…」に失敗しました: …"). The command palette
matches the translated label, the English label and the command id.
Vertical Japanese PDF rendering is an existing viewer feature; this
change does not add vertical text editing. Document contents, filenames,
CLI/MCP/control protocol strings and external help stay outside the
catalog. Per-language coverage, script support and what each language
still needs are in localization-parity.md.
Whole sentences that the layers below word — what the accessibility checker found, what the PDF/A verifier reports, what a signature's details say, what is wrong with the pages to print, what an edit is called in the history — are translated too, because those layers hand the sentence over apart from the document's own words. See Sentences the engine words.
The Ukrainian catalog has 2,047 entries, written from the meaning of
the English labels using Ukrainian computing vocabulary. Commands use
infinitives; fixed-count messages use count-neutral wording.
Plural-aware messages use the three CLDR
Ukrainian integer forms: 1 сторінка,
2 сторінки, 5 сторінок; 11–14 take the third
form, while 21 and 22 return to the first and second. Calendar dates use
genitive month names (9 жовтня 2026), while headings use
standalone names (Жовтень 2026). Home recommendation cards
grow to fit translated text; the toolbar search hint truncates before
the shortcut, with the full label available on hover.
The Lithuanian catalog has 2,585 entries and three integer plural forms. Calendar headings use standalone month names; dates use genitive forms. Calendar, inspector and password-dialog UI regressions check dynamic labels and preserve document values. A native Lithuanian-speaking contributor has reviewed the translation.
The Vietnamese catalog has 2,674 entries and one plural form
(plural_none), covering the current catalog-key union.
Preferences theme Appearance uses context
theme → Giao diện; object/comment/signature
appearance stays plain Diện mạo. Form fields use
Trường; redaction apply wording states permanent removal.
Calendar months are numeric (Tháng 1…12 /
tháng 1…12). A native Vietnamese-speaking
contributor has reviewed the translation. Status stays
partial: no translated help, and engine/OS details stay
English inside a translated frame.
The Finnish catalog has 2,762 entries: every key of the other bundled
catalogs, written from the English labels in ordinary Finnish computing
vocabulary and sentence case. Finnish has the English one/other plural
rule, with the partitive singular after any number but 1
(1 sivu, 2 sivua, 0 sivua).
Calendar headings use the nominative month (Lokakuu 2026);
dates use the partitive (14. lokakuuta 2026), and Finnish
is also a date language for month and weekday names. Any
fi-* locale selects it; sv-FI keeps English
until Codex has a Swedish catalog. The bundled Latin faces cover
ä ö å š ž. The translation is AI-assisted (Claude Code) and
has not yet been proofread line by line by a native speaker.
The Catalan catalog has 2,592 entries, written from the meaning of
the English labels in standard Catalan computing vocabulary. Commands
use the second-person imperative (Desa, Obre,
Cancel·la) and notices use impersonal or informal wording.
Catalan has two plural forms like English (1 is singular; 0 and every
other count take the plural: 1 pàgina,
3 pàgines). Every ca-* locale, including
Valencian (ca_ES@valencia), uses this catalog. The
interpunct of l·l is U+00B7, which the bundled Latin faces
cover (tested in tests/it/fonts.rs). The date picker has
Catalan month and weekday names; dates are day-first like French
(dddd d mmmm yyyy), but after a day the month carries its
own preposition, elided before a vowel (7 de març,
7 d'abril, 8 d'octubre), as Czech and Russian
change theirs through months_with_day; the year takes
\de from the pattern (d mmmm \de yyyy). A
native Catalan speaker (the contributor) has reviewed the
translation.
The Romanian catalog (ro, Română) has 2,673 entries and
three plural forms (plural_ro, CLDR ro):
one for 1, few for 0 and counts whose last two
digits are 1–19 ("2 pagini", "101 pagini"), other for the
rest, written with "de" ("20 de pagini"). Commands use the noun form
common in Romanian software ("Salvare", "Deschidere…"); messages address
the user formally. Redaction is "Anonimizare" ("redactare" means editing
in Romanian), a watermark "Filigran". Calendar month names are lowercase
inside dates ({day} {month} {y}). The bundled Inter faces
cover ă, â, î, ș, ț (comma below). Not yet reviewed by a second native
speaker; status partial: no translated help, and engine/OS
details stay English inside a translated frame.
The Norwegian Bokmål catalog covers every key the other bundled
catalogs have, with the English one/other plural rule, Norwegian key
names (Mellomrom; Shift keeps its name) and month and
weekday names for Fill & Sign dates. It was written from the meaning
of the English labels; a native-speaking contributor reviewed its
terminology (key names, Opacity, Redact, Highlight, Fill & Sign),
not yet every line.
Edit text reads Hebrew lines in typing order and writes them back in
drawing order (crates/edit/src/bidi.rs); letters the page's
own font lacks are drawn with the craft-fonts Hebr face
(Noto Sans Hebrew) as Type 3 glyphs, and a build without one says so.
Hebrew points (niqqud) can't be written yet in Edit text. Add text
(#128) draws Hebrew with the same face through the shaped path Arabic
and Thai use: right to left with numbers and Latin in place, points kept
on their letter, and typing order for copy and search. The craft-fonts
revision pinned for CI and releases has no Hebr face yet,
so until one is added releases report that error instead of drawing the
missing letters, and refuse Hebrew in Add text.
Japanese interface text uses BIZ UDPGothic from craft-fonts, an
optional build input that every release includes
(CRAFT_FONTS_DIR; see the README). A build made without it
has no Japanese face, so Japanese labels show replacement boxes. Chinese
is drawn with the same craft-fonts CJK faces (in Simplified Chinese the
Hans faces come first, so a line never mixes faces with
different baselines), so a character none of them has shows a
replacement box (and in a build without craft-fonts every Chinese
character does, on the web and on a desktop machine with no installed
Chinese face). Until the interface embeds a Hans face, the
desktop app draws a Chinese interface with a Chinese face already
installed on the machine (Microsoft YaHei or JhengHei on Windows,
PingFang, Hiragino Sans GB or STHeiti on macOS, WenQuanYi or Droid Sans
Fallback on Linux), placed before the Japanese faces so a line keeps one
face (#826). The web build has no installed fonts to fall back on.
Russian, Bulgarian, Czech, Brazilian Portuguese, German, Spanish,
French, Hungarian, Italian, Slovenian, Turkish, Lithuanian, Norwegian,
Finnish, Albanian, Catalan and Romanian need only the bundled Latin
faces (tested in tests/it/fonts.rs). Slovenian has four
plural forms: one (1, 101, 201…), two (2, 102…), few (3, 4, 103…) and
other (everything else, including 0, 5–20 and 21+), so
@plural entries list four forms (1 stran,
2 strani, 3 strani, 5 strani).
Hungarian uses the same one/other plural rule as English: 1 is singular,
and every other count — including 0 — is the other form. After a numeral
the noun stays singular (1 oldal, 2 oldal).
Telugu is drawn with a craft-fonts Telu face (Noto Sans
Telugu, kept in the web build too), whose conjuncts egui shapes; a build
without one shows Telugu labels as replacement boxes, and the
release-pinned craft-fonts input has no Telugu face yet.
The Brazilian Portuguese catalog has 2,674 entries and covers the
current union of catalog keys. It began with the ~1,826-entry gist André
Cardoso posted in #288, with
labels added since and the existing wording reviewed. Portuguese, like
French, treats 0 and 1 as singular (1 página,
2 páginas).
The Albanian catalog (sq.tsv, interface name
Shqip) is written in Standard Albanian with the
imperative for commands (Ruaj, Hap,
Fshi). Albanian uses two plural forms like English: 1 is
singular and every other count, including 0, is plural
(1 faqe, 2 faqe). Month and weekday names are
lowercase (mars, e shtunë), and the letters
ë and ç are covered by the bundled Latin
faces. It has not been reviewed by a native Albanian speaker yet.
Arabic and right-to-left text
The Arabic catalog has 2,137 entries in Modern Standard Arabic with
Western digits. Counts use the six CLDR
Arabic forms (0, 1, 2,
3–10, 11–99, the rest), and dates use the
Arabic names of the Gregorian months.
egui joins the letters of an Arabic word and runs them right to left,
but it places the words of a line left to right in the order they are
written. So catalog entries are written in logical (typing) order, and
i18n puts each one into display order when the catalog
loads (bidi::display_rtl): the line reads right to left,
Latin words and numbers inside it read left to right,
{name} placeholders and keyboard shortcuts stay whole,
spaces at either end stay where the code concatenates, and a short
caption keeps its colon on the right, beside the widget it captions.
Lookups (tl!, tr, trn) therefore
return display-order text for Arabic; i18n::fmt inserts
values as they are. The command search matches a query of several Arabic
words word by word.
What Arabic does not have yet:
- The layout is not mirrored. Panels, menus, toolbars and alignment are as in the left-to-right languages, with Arabic labels in them.
- A wrapped paragraph breaks at the wrong end. A label too long for its line continues on the next line with its first words, so wrapped descriptions and tooltips read from the last line up. Single-line labels are right.
- Text you type into a field (search, comments, form values) has joined letters but its words run left to right while you edit it. What is stored is correct.
- Labels built by joining translated pieces in code (a list such as
A · B · C) keep the code's left-to-right order.
These need bidirectional support in the text layout itself, not in the catalog.
Central Kurdish (Sorani, ckb) takes the same path: its
catalog is written in logical order and rtl: true puts
every entry into display order at load (bidi::display_rtl),
so it behaves as Arabic does above and has the same four limits. Counts
use the two CLDR
ckb forms (one, other); Sorani keeps the
noun singular after a numeral, so the two @plural forms
usually read the same. Dates use the Kurdish names of the Gregorian
months. The Arab face (Noto Sans Arabic) covers the
Kurdish-only letters (ێ ڵ ڕ ۆ ە ڤ ژ چ گ پ).
Arabic glyphs come from the craft-fonts Arab face (Noto
Sans Arabic, kept in the web build too). A build without craft-fonts
shows Arabic as replacement boxes on the web; on desktop it falls back
to one installed font (below). The craft-fonts revision pinned for CI
and releases (.github/workflows/) has the Arabic face. That
revision also holds Noto Sans CJK SC, which
crates/fonts/build.rs leaves out for size (16.4 MB whole),
so Chinese still has no embedded face of its own (desktop builds fall
back to an installed Chinese face, above).
File names and document titles in Arabic or Hebrew are drawn with a
craft-fonts Arab face when the build input has one; on
desktop, a character no embedded face has is drawn with one font already
installed on the machine (CODEX_SYSTEM_FONTS=0 turns this
off). The tab strip, the recent-files list and Properties show such
names in display order; other places (bookmarks, comments, search
results) have the glyphs but may order mixed-direction text wrongly.
Catalog coverage does not guarantee glyph coverage: the craft-fonts
revision pinned for CI and releases has no Hans face small
enough to embed (desktop builds embed a Hans face of at
most 4 MiB from CRAFT_FONTS_DIR), and the web build embeds
only BIZ UDPGothic Regular. Chinese font delivery remains separate work,
as discussed in #112 and #140.
German labels keep nouns capitalized, including tool names inserted
into notices. The certificate viewer uses the certificate
context for Subject (the certificate holder), while
document properties keep the plain translation (the topic). Search hints
reserve room for the shortcut, and Home cards limit their title and
description to the available rows; hovering shows the full text when it
is shortened.
How translations work
Codex uses the same system as Picto
(crates/ui-egui/src/i18n/):
- Strings in the code stay in English and serve as lookup keys. At
draw time,
tl!("Save")returns the text in the current language, and falls back to English when there is no entry. - Each language has a catalog,
crates/ui-egui/src/i18n/<code>.tsv, with one entry per line:context<TAB>English<TAB>translation. An empty context marks a plain string.@idkeys an entry by command id (for examplefile.saveAs), so one menu item can read differently from another with the same English label.@pluralholds plural forms. Any other context disambiguates an English word with several meanings (tl_ctx!in UI code, for example the signature pad's "Type" button against the "Type" column). The header ofja.tsvdocuments the format and escapes. - Placeholders (
{name}) must appear in both columns. A trailing…must be kept.i18n::fmtfills placeholders in one pass, so a value such as a file name containing{n}is inserted as written. - A missing entry never fails a build or a test: that string shows in
English.
cargo xtask i18n-report, a step ofcargo xtask ci, prints for each language how many strings it has no translation for and the first few. It counts every command, All tools label, literal passed totl!ori18n::tin the UI source, engine wording (below) and key of the bundled catalogs, so a new English string shows up there until each catalog has it. - Catalogs are validated strictly: unknown escapes, unknown
@contexts, placeholder or ellipsis mismatches, wrong plural form counts and duplicates are errors. The tests require the bundled catalogs to have none; at run time a bad line is skipped (and logged) and that string shows in English. - Each
.tsvhas anATTRIBUTION.tomlentry withkind = "translation"(title, author, source, licence), checked bycargo xtask assets. Catalogs are project files under the project licence, so the entry has nosha256and editing a catalog doesn't touchATTRIBUTION.toml; git history records their provenance. - Translations are clean-room: write them from the meaning of the English text, never from another product's string tables.
Sentences the engine words
Some sentences are not the interface's own. The accessibility
checker, the PDF/A verifier, the signature reader, the page-range parser
and the edit history all word their own findings, and matching that
wording in the UI with a tl! literal would break silently
whenever the engine changed it. So those layers hand the wording over
instead, apart from the document's own words:
- Each of them has a
WORDINGSconstant listing every sentence it can say, as&'static strwith holes in it ({n},{p},{}). The sentence is written once, in the layer that means it. said(&words)says one of them: the layer looks its own wording up through the closure it is given, then fills the holes with the document's values — a page number, a font name, what the person typed — in one pass, so a value containing{n}is inserted as written.- The UI passes
&crate::i18n::words, which is a catalog lookup, so the sentence reads in the interface language.in_englishis the same thing in English, and a wording with no entry comes back as it stands, which is the English — a catalog missing one shows an English sentence rather than a gap. - The lists are
a11y::{WORDINGS, REPORT_WORDS},preflight::WORDINGS,sign::pdf::{WORDINGS, kind::ALL},print::WORDINGSand the engine's{EDIT_LABELS, BATCH_LABELS}.a11y::report_htmltakes a language tag and awordsclosure, so a saved report reads the way the panel read, down to itslangattribute. - The translation gaps report (
cargo xtask i18n-report) counts every wording in those lists, so a catalog that lacks one shows there; until it is added, that sentence shows in English. Catalog validation requires each translation to keep the same holes, andspanish_keeps_the_holes_in_what_the_engine_sayschecks the Spanish ones against the lists themselves.
Error details that come from the operating system, and faults rather than mistakes (a file that cannot be read, a lock that was poisoned), still carry their own text and are shown as they are.
Adding a language
- Copy the header of
ja.tsvintoxx.tsvand translate entries. - Add one row to
LANGUAGESincrates/ui-egui/src/i18n/mod.rs: code, native name, catalog and plural rule. - Add an
ATTRIBUTION.tomlentry for the catalog (kind = "translation", nosha256), then runcargo xtask assets --write(it regeneratesATTRIBUTION.mdonly) andcargo xtask assets.
The Preferences dropdown, the system-language match, the catalog tests (format, duplicates, placeholders, plural forms, command ids) and the translation gaps report then pick it up.