Extract, translate, import, repeat. The text stays owned by your code or string catalog; XLIFF is the exchange format that carries each string with its id and context to the translators and brings the translation back.
.xcloc catalog per language with the XLIFF inside. Angular's ng extract-i18n writes messages.xlf, which you copy to messages.de.xlf and so on. Symfony's php bin/console translation:extract --force de writes translations/messages.de.xlf. The result is one file per target language with source-language and target-language on <file>. Untranslated units have no <target>, an empty one, or <target state="new">; Symfony fills new targets with a __ prefix unless you pass --no-fill.<target> and the tool records progress in the state attribute (translated, then signed-off or final) or with approved="yes" on the <trans-unit>. Placeholders such as <x id="INTERPOLATION"/> are protected tags there, which is the main reason not to translate XLIFF in a text editor.angular.json at build time; Symfony loads translations/ at runtime. Xcode and Angular match translation units by id, so re-importing updates existing translations instead of duplicating them.Rules that keep this working round after round:
id values stable between exports. Angular's generated ids are derived from the source text and meaning, so a text change makes a new id and orphans the old translation; give units a custom id (i18n="@@welcomeTitle") and never change ids in translation files, as the Angular docs say. Xcode uses the string key as the id.resname and <note> for context. resname carries the resource name (the key) when the id is synthetic; <note> carries developer comments, which is where Xcode puts the comment: text and Angular puts the description and meaning. Translators see both in their tool.<source> in a translated file. The next extraction overwrites it, and translation memory matches on the source text; change the string in code and re-extract.Published Oct 9, 2026 · Markdown version