Fix Dropdown issue
This commit is contained in:
178
po/README.md
Normal file
178
po/README.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Translations (i18n)
|
||||
|
||||
Everything about the catalogs, in one place: which file is which, how a string gets
|
||||
from the source code into a translated window, and how to add a language. The
|
||||
commands live in `scripts/i18n.sh`, wired into the Makefile as `pot`,
|
||||
`i18n-check` and `i18n-status`.
|
||||
|
||||
## The pieces
|
||||
|
||||
| file | what it is |
|
||||
| --- | --- |
|
||||
| `po/gkeymapper.pot` | the template: every translatable string in the sources, with the file and line it comes from. **Generated** by `make pot` — never translated, and its msgids are never edited by hand. |
|
||||
| `po/<lang>.po` | one catalog per language. This is what a translator edits. |
|
||||
| `locale/<lang>/LC_MESSAGES/gkeymapper.mo` | the compiled catalog the program reads at runtime. Generated by `make translations`, gitignored. **Never edit** — it is overwritten on every build. |
|
||||
| `scripts/i18n.sh` | `extract`, `update`, `check`, `status`, `todo`, `new` (see below). |
|
||||
|
||||
Languages: `de`, `en`, `es`, `fr`. The set is `po/*.po` — the Makefile picks up a
|
||||
new catalog by itself (`LANGS`), nothing has to be registered.
|
||||
|
||||
`en.po` is special: English is the source language, and its catalog repeats every
|
||||
msgid as its msgstr (an *identity* catalog). It exists so that choosing
|
||||
"English" works on a German desktop, where the session locale would otherwise
|
||||
select the German catalog. `i18n-check` enforces the identity — a translation that
|
||||
drifts in is reported.
|
||||
|
||||
## How a string reaches the window
|
||||
|
||||
1. `_("...")` marks it in `src/` (or `shared/`) — the two directories the GUI is
|
||||
built from. `N_("...")` is for strings that are only *collected* when they are
|
||||
not translated yet: `src/help.cpp` keeps its paragraphs in a static array and
|
||||
translates them when the window is built (`_(text)`). Both keywords are in the
|
||||
pot; a string marked with neither is invisible to translators.
|
||||
2. `make pot` writes `po/gkeymapper.pot` from the sources.
|
||||
3. `scripts/i18n.sh update` merges the pot into every catalog. New strings come in
|
||||
with an empty msgstr (= untranslated), existing translations are kept.
|
||||
4. Translating is editing `po/<lang>.po`: replace the msgstr, leave the msgid
|
||||
alone. Any text editor, or `poedit` if you prefer one.
|
||||
5. `make translations` compiles `locale/<lang>/LC_MESSAGES/gkeymapper.mo`.
|
||||
6. The program reads the catalog whose language it was started with. The GUI picks
|
||||
it up automatically once gettext is set up in `src/main.cpp`; *which* language is
|
||||
used comes from the options window (stored in `~/.config/gKeyMapper/gui.conf`,
|
||||
`language=de`), or from the session when that setting is empty. Internally
|
||||
`apply_gui_language()` (`src/settings.cpp`) sets `LANGUAGE` and re-runs
|
||||
`setlocale` — `LANGUAGE` wins over `LANG`/`LC_ALL` in gettext, which is what
|
||||
makes switching without a restart and an explicit `en` on a German desktop work.
|
||||
The catalogs are searched under `$GKEYMAPPER_LOCALEDIR`, falling back to the
|
||||
compiled-in `LOCALEDIR`.
|
||||
|
||||
## The common case: a string is added or changed
|
||||
|
||||
```bash
|
||||
# 1. in the code
|
||||
_("New text") # or N_("...") for a deferred one
|
||||
|
||||
# 2. the template
|
||||
make pot # prints what is new and what is gone
|
||||
|
||||
# 3. what is missing where
|
||||
make i18n-check # names every catalog that lacks it
|
||||
|
||||
# 4. every catalog takes it in (empty msgstr)
|
||||
scripts/i18n.sh update # or: scripts/i18n.sh update de
|
||||
|
||||
# 5. translate
|
||||
scripts/i18n.sh todo de # untranslated + fuzzy, with the source location
|
||||
$EDITOR po/de.po # and es, fr - and en, where the msgstr is the msgid
|
||||
|
||||
# 6. check and compile
|
||||
make i18n-check # must print "everything in sync"
|
||||
make translations
|
||||
|
||||
# 7. see it
|
||||
GKEYMAPPER_LOCALEDIR=$PWD/locale LANGUAGE=de_DE.UTF-8 ./gkeymapper
|
||||
```
|
||||
|
||||
Commit the source change, `po/gkeymapper.pot` and the catalogs **together**: a pot
|
||||
without the catalogs is a half-done change, and `make i18n-check` says so.
|
||||
|
||||
A new string in one place and the same text in another is *one* message for
|
||||
gettext — the pot lists both locations, and one msgstr covers both.
|
||||
|
||||
## Adding a language
|
||||
|
||||
```bash
|
||||
scripts/i18n.sh new it Italiano # po/it.po, header in the project's shape,
|
||||
# every msgstr empty (the second word is
|
||||
# Language-Team; it defaults to the code)
|
||||
scripts/i18n.sh todo it # work through it
|
||||
$EDITOR po/it.po
|
||||
```
|
||||
|
||||
Then offer it in the GUI, or nobody can select it — `src/optionsdialog.cpp`:
|
||||
|
||||
* `kLanguageCodes[]` — the codes, in the same order as the labels ("", the empty
|
||||
one, means "follow the session" and has to stay first);
|
||||
* `languageOptions[]` — the labels. Use the **endonym** ("Italiano", not
|
||||
"Italian"): the list reads the same in every language, so a user who lands in
|
||||
the wrong language can still find their own.
|
||||
|
||||
`make i18n-check` checks exactly that pairing (a catalog that cannot be selected, a
|
||||
code without a catalog, two arrays of different length). `make translations`
|
||||
compiles the new catalog; `make run` and the test command above use the working
|
||||
tree's `locale/`.
|
||||
|
||||
## Commands
|
||||
|
||||
| command | what it does |
|
||||
| --- | --- |
|
||||
| `scripts/i18n.sh extract` (`make pot`) | Regenerate `po/gkeymapper.pot` from the sources. Prints the messages that appeared and disappeared. Refuses to write an empty pot, so a broken gettext cannot empty the good one. |
|
||||
| `scripts/i18n.sh update [lang...]` | Merge the pot into the catalogs, without source locations (see Conventions). Keeps translations, marks new strings untranslated. |
|
||||
| `scripts/i18n.sh check` (`make i18n-check`) | Everything below. Exit code 1 when something is wrong, so it also works as a pre-commit step. |
|
||||
| `scripts/i18n.sh status` (`make i18n-status`) | Per language: messages, translated, fuzzy, untranslated, and whether the compiled catalog is older than the po. |
|
||||
| `scripts/i18n.sh todo <lang>` | The untranslated and fuzzy messages, with the source locations taken from the pot. The output is a valid po file, if you want to keep it. |
|
||||
| `scripts/i18n.sh new <lang> [team]` | Create `po/<lang>.po` from the pot. `new en` writes the identity catalog. |
|
||||
|
||||
The gettext tools are taken from `PATH` and can be overridden like the Makefile's
|
||||
variables, e.g. `XGETTEXT=/usr/bin/xgettext scripts/i18n.sh extract`.
|
||||
|
||||
## What `check` looks at
|
||||
|
||||
* the pot against the sources: same messages? A string added or changed without
|
||||
`make pot` is the mistake this catches (a `#:` line number that moved is only
|
||||
reported as a note — refresh it whenever it suits you);
|
||||
* every catalog: it parses (`msgfmt -c`, which also compares the format
|
||||
specifiers of msgid and msgstr), it defines the same messages as the pot, nothing
|
||||
is untranslated or fuzzy, and its `Language:` header matches its file name;
|
||||
* `po/en.po` is an identity catalog;
|
||||
* `po/*.po` and the language list of the options window (`src/optionsdialog.cpp`)
|
||||
agree;
|
||||
* no strings are marked in `daemon/`: the daemon has no gettext setup, so `_()`
|
||||
there would be translated into a catalog that is never read.
|
||||
|
||||
Notes that are *not* failures: catalogs that were not merged since the last
|
||||
extract, and obsolete entries (`#~`, strings that are gone from the sources —
|
||||
`msgattrib --no-obsolete po/<lang>.po > tmp && mv tmp po/<lang>.po` drops them).
|
||||
|
||||
## Conventions
|
||||
|
||||
* **No `#: file:line` comments in the catalogs.** The locations are noise for a
|
||||
translator and churn whenever a line moves, so they live only in the pot
|
||||
(`update` therefore uses `msgmerge --no-location`) and `todo` shows them from
|
||||
there when they are needed. This is also why the catalogs are merged with the
|
||||
script instead of a bare `msgmerge --update`, which would copy the locations in.
|
||||
* **The build does not regenerate the pot** (`make translations` only runs
|
||||
`msgfmt`). A template that is rewritten by every build is a template nobody
|
||||
looks at; instead `make pot` is a deliberate step and `make i18n-check` fails
|
||||
when it was forgotten.
|
||||
* **Mnemonics stay mnemonics.** `_Cancel` uses the `_` to underline a letter;
|
||||
`_Abbrechen` moves it to a letter that exists in that language. A translation
|
||||
without the underscore loses the keyboard shortcut silently.
|
||||
* **Format specifiers** (`%s`, `%d`) keep their number and order — `msgfmt -c`
|
||||
catches a mismatch.
|
||||
* **No plurals yet**: the program only uses `_()`, so no catalog carries a
|
||||
`Plural-Forms` header. With the first `ngettext()` that header has to go into
|
||||
every catalog (and `scripts/i18n.sh new` should write it).
|
||||
* **HTML-ish markup** in a msgid (`<b>`, `<i>`) has to survive in the msgstr; the
|
||||
help window renders it.
|
||||
* German belongs in `po/de.po` only — sources, comments and documentation are
|
||||
English.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* *A window stays English while another window is translated* — the string is
|
||||
missing from the catalog (or the catalog was not recompiled: `make translations`).
|
||||
`scripts/i18n.sh todo <lang>` lists what is open.
|
||||
* *Shortcut without an underline* — the mnemonic `_` is missing in the msgstr.
|
||||
* *`po/*.po~` files appear* — that is `msgmerge`'s backup from a manual
|
||||
`msgmerge --update`; the script deletes it. Never commit them.
|
||||
* *A fuzzy entry is shown in English* — on purpose: fuzzy means "the text was
|
||||
changed, this translation needs a look", and gettext falls back to the msgid
|
||||
until it is confirmed (remove the `#, fuzzy` line).
|
||||
* *"not merged against the current pot" in `check`* — run `scripts/i18n.sh update`;
|
||||
until then the catalog header carries an older date than the pot.
|
||||
* *Nothing is extracted at all* — `xgettext`'s built-in keyword list is not the
|
||||
same everywhere, so the script always passes `--keyword=_ --keyword=N_`. If the
|
||||
pot comes out empty, the script stops instead of replacing the pot.
|
||||
|
||||
The rest of the project's conventions (GUI, daemon, mappings) are in `AGENTS.md`.
|
||||
Reference in New Issue
Block a user