Skip to content

Big Translation and fmt string formatting rework#1567

Merged
JonnyH merged 8 commits intoOpenApoc:masterfrom
JonnyH:WIP/fmt-strings-translation
Jan 13, 2026
Merged

Big Translation and fmt string formatting rework#1567
JonnyH merged 8 commits intoOpenApoc:masterfrom
JonnyH:WIP/fmt-strings-translation

Conversation

@JonnyH
Copy link
Collaborator

@JonnyH JonnyH commented Jan 11, 2026

Moves everything to fmt-style {0} format specifiers instead of
printf-style %s etc.

This has a BIG effect on translations, so do a complete rework of that
at the same time.

The end goal is that all code translated strings (with tr()) will use
numbered fmt format specifiers - e.g "string {0} precedes {1}" so they
can be re-ordered by translations as necessary.

Much of this conversion was automated using clang-tidy, and was
somewhat tested, but keep an eye out for issues.

While doing this, rewrite a number of areas that construct strings from
fragments - translations would much prefer to be able to
translate/reorder the block as a whole.

All this made the old POT template pretty useless, also not helped by
the fact that seemed to have been "seeded" by dumping the string table
of the original executable - so actually contained a large number of
strings we never used.

Note that forms and "game data" (like names for things) are excluded
from the gettext-based code translation. The goal is to allow form XML
and gamestate to be modified on a per-mod per-language basis. Though
arguably much of the Gamestate is proper nouns - which may not really
require translation, there are some parts that will, ufopaedia entries, for example. Honestly the ufopaedia entry should probably not be stored in the game state, the text itself isn't something anything really depends on or needs to persist changes

To allow this, mods can now add data directories and gamestate patches
on a per-language basis. For example, the modinfo can contain:

<languages>
		<entry>
			<ID>lang_COUNTRY.UTF-8</ID>
			<patch>lang/gamestate/lang_COUNTRY</patch>
			<data>lang/data/lang_COUNTRY</data>
		</entry>
</languages>

The game tries to match a locale that matches the current set language as
set in the config and launcher. They all are suffixed in .UTF-8, as we only
support that encoding.

First an exact matching language_COUNTRY ID is checked - e.g.
"en_GB.UTF-8" for British English. If no match, the country is
stripped, so it next searches for "en.UTF-8".

Both the patch and data are optional - either leave empty or have no tags
if the mod doesn't require them.

Currently the "first" mod in the list is read by the launcher to populate the
"supported languages" list - that's probably the base mod. So really only
that needs to list every possible supported language, other mods only need
to list those that actually require changes.

Also then updates to fmt 12.1.0, latest tag at time of writing.
Because why not

Pretty much all the strings will be replaced
Moves everything to fmt-style {0} format specifiers instead of
printf-style %s etc.

This has a BIG effect on translations, so do a complete rework of that
at the same time.

The end goal is that all code translated strings (with tr()) will use
numbered fmt format specifiers - e.g "string {0} precedes {1}" so they
can be re-ordered by translations as necessary.

While doing this, rewrite a number of areas that construct strings from
fragments - translations would *much* prefer to be able to
translate/reorder the block as a whole.

All this made the old POT template pretty useless, also not helped by
the fact that seemed to have been "seeded" by dumping the string table
of the original executable - so actually contained a large number of
strings we never used.

Note that forms and "game data" (like names for things) are excluded
from the gettext-based code translation. The goal is to allow form XML
and gamestate to be modified on a per-mod per-language basis. Though
arguably much of the Gamestate is proper nouns - which may not really
require translation.
The idea is that mods can add gamestate patches and append data search directories.

The idea is that the *vast* majority of the Gamestate doesn't need translation - proper nouns etc. and the game setting is in an English-speaking country. But I'm allowing that just in case there are things in there that probably should be translated.

And the data override allows 2 major things - forms and image replacement. The idea is that a language that changes strings on a Form replaces the corresponding Form XML. This allows rearrangement, reflowing and resizing as necessary. Same with images - as many UI images contain text.

This does have a limitation of that only the "latest" in the data stack for each file is read - if two mods modify the same form, for example, only the last in the load order will be visible to the game.

Note: we use language codes as returned by boost - that "language_COUNTRY" format - e.g. "en_GB"/"en_US". The idea is that if the country isn't available, it falls back to the parent language - e.g. "en_GB" would fall back to an "en" folder and patch file, if such a things exists.
Mostly enum casts for debug output and transitive header changes
These seem to differ between the clang-format 18.1.8 I have locally

And seem to be somewhat weird an inconsistent - different spaces for
different constructors?

eh, whatever.
@JonnyH JonnyH force-pushed the WIP/fmt-strings-translation branch from 502052e to 2844ec0 Compare January 12, 2026 20:12
@JonnyH JonnyH force-pushed the WIP/fmt-strings-translation branch from 2844ec0 to 47519d4 Compare January 12, 2026 20:12
@JonnyH JonnyH merged commit 53deaf1 into OpenApoc:master Jan 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant