Your App's Locale Codes Aren't Fastlane's Locale Codes
Published 10 September 2026
Quick answer: In-app localisation and App Store Connect metadata use different locale identifiers.
Localizable.xcstringsusesde,fr,es,ar;fastlane/metadata/needsde-DE,fr-FR,es-ES,ar-SA. A barede/directory makesdeliverabort, and it aborts after uploading the locales it did understand.
You add German to an app. In the String Catalog it's de. So you create fastlane/metadata/de/, fill in the description and keywords, run the metadata lane, and it fails.
The reason is that these are two unrelated systems that happen to both call their identifiers "locales". Xcode uses language codes. App Store Connect uses its own list, and several entries are region-qualified.
The mapping that catches people
| Language | In-app (xcstrings) |
Metadata (fastlane/metadata/) |
|---|---|---|
| German | de |
de-DE |
| French | fr |
fr-FR |
| Spanish | es |
es-ES |
| Arabic | ar |
ar-SA |
| Portuguese (Brazil) | pt-BR |
pt-BR |
| Japanese | ja |
ja |
| Korean | ko |
ko |
| Chinese (Simplified) | zh-Hans |
zh-Hans |
The inconsistency is the trap. Japanese and Korean are bare, German and French are not, and there's no principle to derive it from — you either know or you find out during a release.
Also note es-ES is Spain specifically; Latin American Spanish is es-MX, a separate storefront locale. And en-US and en-GB are distinct, which matters if your copy uses "color" or "colour".
Why the failure is annoying
deliver validates locale directories as it goes rather than up front. So an unsupported directory aborts the lane after it has already uploaded the locales it understood.
You're left in a partial state: some locales updated, some not, and a lane you have to re-run. Nothing is broken, but "did that upload or not?" is a question you now have to answer by looking at App Store Connect.
Check before you run
A three-line guard beats reading the error:
VALID="ar-SA ca cs da de-DE el en-AU en-CA en-GB en-US es-ES es-MX fi fr-CA fr-FR he hi hr hu id it ja ko ms nl-NL no pl pt-BR pt-PT ro ru sk sv th tr uk vi zh-Hans zh-Hant"
for d in fastlane/metadata/*/; do
loc=$(basename "$d")
case " $VALID " in
*" $loc "*) ;;
*) echo "unsupported metadata locale: $loc" ;;
esac
done
Skip the non-locale directories your setup uses — review_information, trade_representative_contact_information and similar live alongside the locales and aren't locales.
While you're checking, check the lengths
The other thing that fails an upload late is a field one character over its limit. The limits worth guarding:
- App name — 30 characters
- Subtitle — 30
- Keywords — 100, commas included
- Promotional text — 170
- What's New — 4,000
One over and the upload is rejected, per locale, after other locales have gone up.
A detail that costs a confusing minute: wc -m counts the trailing newline. A 30-character name in a file reports 31. Strip it before comparing, or you'll "fix" a field that was already fine:
printf '%s' "$(cat fastlane/metadata/en-GB/name.txt)" | wc -m
The general shape
Both of these are the same class of problem: a validation that happens remotely, per-item, partway through a batch. The fix is the same too — validate locally before you start, because the local check is instant and the remote one costs you a partial upload and a rerun.
Worth putting in a preflight lane so it runs on every release rather than when someone remembers.
Related posts
- A .DS_Store File Will Break Your Screenshot Upload Upload tools that read image dimensions try to decode every file in the directory — so a Finder artefact you can't see aborts the whole batch with 'image: unknown format'.
- AttributedString(markdown:) Throws Away Your Paragraphs With default options, AttributedString's markdown parser joins paragraphs with no separator and drops list bullets — turning model output into run-on text. The option that fixes it.
- xcodebuild Might Be Building Release Without Telling You Omit -configuration and xcodebuild uses whatever the scheme says, which is often Release. The tell is an empty Debug-iphonesimulator directory and a product you can't find.