File formats & standardsこの記事を日本語で読む

Apple's .strings and .stringsdict: how iOS and macOS store translated text

Apple's platforms have used a family of plain-text and XML resource formats for localizable text since long before Swift existed, and the underlying concepts are still what you find under the hood of newer tooling. Understanding .strings and .stringsdict directly is useful even if a project's day-to-day editing happens through a different interface, because these are still the files that ship inside the app bundle and the files a translator ultimately has to get right.

The key-value .strings format

A .strings file is a flat list of key-value pairs, one per localizable string, stored as a property list in a text notation that looks close to C string literal syntax. Each entry is a quoted key, an equals sign, a quoted value, and a terminating semicolon.

/* Shown on the welcome screen after login */
"welcome_title" = "Welcome back";

/* Confirmation before logging the user out */
"logout_confirm" = "Are you sure you want to log out?";

The comment above each entry is not decoration — it is the conventional place to give a translator context that the key name alone cannot: which screen the string appears on, whether it is a button label with a length constraint, or what a placeholder inside the string will be replaced with. A key like welcome_title tells a translator almost nothing on its own; the comment is where the actual briefing lives.

Like Android's resource files, a project ships one .strings file per language, each at a matching path inside a language-specific .lproj directory, and the app selects the right one at launch based on the user's preferred languages. A key present in the base file but missing from a translated one falls back to the base language silently, which makes periodic key-completeness checks across all .lproj directories worth automating rather than trusting to memory.

Format arguments and positional order

String formatting inside .strings values follows the same printf-style conventions used elsewhere on the platform, including positional indices so a translator can reorder arguments to fit the target language's word order without any code change.

"new_messages" = "Hello, %1$@! You have %2$ld new messages.";

.stringsdict for plurals and width variants

A flat key-value file has no way to express that a sentence's wording changes with a number, and English's two-form plural rule does not generalize to other languages, some of which distinguish several grammatical plural categories. .stringsdict solves this with an XML property list, keyed by the same string key used in the .strings file, that supplies one variant per Unicode CLDR plural category — zero, one, two, few, many, other — for a given format specifier.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>items_selected</key>
  <dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%#@items@</string>
    <key>items</key>
    <dict>
      <key>NSStringFormatSpecTypeKey</key>
      <string>NSStringPluralRuleType</string>
      <key>NSStringFormatValueTypeKey</key>
      <string>d</string>
      <key>one</key>
      <string>%d item selected</string>
      <key>other</key>
      <string>%d items selected</string>
    </dict>
  </dict>
</dict>
</plist>

The same mechanism — a format specifier resolved through a dictionary of variants — also covers a second problem: width variants, where a UI element needs a shorter phrasing when space is constrained (a compact watch face versus a full-size screen) and a longer, more natural phrasing otherwise. Both plural and width variants exist because a single hardcoded string cannot represent something that is genuinely conditional on context the app only knows at runtime.

The nesting here is real complexity, and it is why hand-editing a .stringsdict file is more error-prone than editing a .strings file: a malformed plural rule type or a missing category for a language that actually needs it fails silently at runtime, showing a fallback string rather than crashing, so mistakes are easy to ship unnoticed.

String catalogs, conceptually

More recent Apple tooling introduces a string-catalog concept that consolidates a project's keys, base-language text, translations, and plural/width variants into a single file per project rather than a scattered set of .strings and .stringsdict files across .lproj directories. Conceptually it is the same information — keys, comments, plural categories — just stored together so editors can show all languages for a key side by side instead of requiring a file per language to be opened separately.

The underlying concepts carry over regardless of which storage format a given project uses: a stable key, a comment giving context, one base value, and per-language values that must exist for every key or silently fall back.

XLIFF and the translation round trip

Getting text out to a translator and back rarely means handing over raw .strings and .stringsdict files. Apple's tooling can export a project's localizable content as XLIFF, the XML-based Localization Interchange File Format standardized for exactly this purpose: it packages source text, target-language slots, translator notes, and state information (untranslated, translated, reviewed) in one interchange file per language, independent of the underlying resource format.

A translator or translation tool works entirely in XLIFF without ever touching a .strings file directly, and the export/import step is what re-merges the finished translations back into the project's native resource files. This separation is useful precisely because it means translators do not need to understand plural-rule XML or property-list syntax — they work with source and target segments, and the round trip handles the mapping back into the platform's own format.

Related articles