On this page 10 sections
- Decide your development language and your base locale
- String Catalogs, not scattered Localizable.strings
- en-GB, en-US and the words that actually differ
- Dates, numbers, currency and units
- Images, layout and text that grows
- Pluralisation and strings with arguments
- SwiftUI and String Catalogs
- A testing routine that catches gaps
- Key takeaways
- Frequently asked questions
This is not a general accessibility article — for Dynamic Type and VoiceOver start with accessible iOS apps in SwiftUI — and it is not an MVP scoping exercise. It is the localisation method I use when the first language is UK English, whether or not a second language exists yet.
Decide your development language and your base locale
Xcode has a development language, and the system has a locale. They are easy to conflate.
The development language is the language you type in source and the language the String Catalog uses as the source of truth. For a UK-first app, I set this to English (United Kingdom) — en-GB — so the default copy is the copy I actually want on a phone in London. If you leave it as generic en, you will write American English by habit, or a mix, and en-GB will become a partial overlay you forget to maintain.
The device locale decides formatting (dates, numbers, currency, first day of the week) and which language table is chosen. A user in the UK with language English and region United Kingdom should get your en-GB strings and UK formats. A user who has picked English (US) should get en-US if you ship it, not a surprise “colour” in a US UI — and not “color” in a UK one.
A practical scheme:
en-GBis always present if you are a UK app. It is not optional flavouring.- Add
en-USwhen you have US users, and translate the words that actually differ rather than duplicating the whole table by habit. Fallback fromen-UStoen-GB(or the other way) should be a conscious choice, not an accident of “we only haveen”. - Do not rely on generic
enas a third, vaguely mid-Atlantic table. One genericenplus a half-finisheden-GBis how half the screens stay American.
In App Store Connect, the product page languages are a separate list. If the app is UK English, the UK product page should match. That is store metadata, not the String Catalog, but the same words should appear in both.
String Catalogs, not scattered Localizable.strings
A String Catalog (.xcstrings) is the current Xcode format for this work. It holds source strings, translations, comments, plural variations and device variations in one file the compiler still turns into the right runtime tables.
Use one catalog for the app’s user-facing copy, and let Xcode extract strings from String(localized:), Text("…"), and the other standard APIs. Add comments in the catalog for any string where a translator — including future you, adding en-US — would otherwise have to guess: “Button on the photo import screen, imperative verb”, not “Label”.
A few habits keep the catalog honest:
- Default to
String(localized: "Save")or aTextliteral, not a hand-maintained table of keys that never appear in source. Extraction only works if the source uses the APIs Xcode knows. - Give stable keys when the English may change.
String(localized: "home.greeting", defaultValue: "Good morning")lets you reword the default without abandoning other languages. - Do not interpolate raw values into a localised string with
\\(name)inside aTextthat is not going through a format. Use format arguments so the translator can move them. A catalog string such as"You have %lld orders"(or the Swift string interpolation form the catalog extracts) can be reordered in another language. - Delete dead strings. A catalog that still lists last year’s onboarding is how you translate something the user will never see, and miss the thing they will.
Older Localizable.strings and .stringsdict files still work, and you can migrate them into a catalog in Xcode. For new work, start with the catalog. Scattered NSLocalizedString calls with inconsistent tables are the thing you are trying to leave.
en-GB, en-US and the words that actually differ
The useful work is a short, shared list of differences you actually put in the UI, not a lecture on British English. Words and conventions that regularly appear in apps:
- colour / color, favourite / favorite, cancelled / canceled, recognised / recognized, organised / organized, licence (noun) / license, practise (verb) / practice, metre / meter when you mean the unit of length, grey / gray.
- postcode / ZIP code, mobile number / cell phone, car park / parking lot, holiday / vacation, flat / apartment, maths / math.
- Dates written 24/12/2026, not 12/24/2026. This is a formatting problem as much as a copy problem; do not hard-code a date string.
- Currency as £3.99, with a proper pound sign, not
GBP 3.99in a UK UI unless you are deliberately showing an ISO code. - Tone. “Please try again” is the same on both sides of the Atlantic; “neighborhood alerts” is not a UK heading.
I keep a short glossary in the repo for the words we have decided, and I search the catalog for the American forms I do not want in en-GB. A word counter is a quick way to size a screen’s copy when a designer asks whether a title will wrap; it does not replace a glossary.
Do not “correct” trademarked UI of other products, API names, or screenshot labels you do not control. Color, localization, and NSLocalizedString stay as they are in code. The user-facing string is the thing you localise.
If you only ship en-GB today, you can still write the glossary so adding en-US later is filling a column, not inventing a process. That is much cheaper than discovering, after a US launch, that every “colour” has to be hunted by hand.
Dates, numbers, currency and units
Never format a date or a price with a hand-built string. Use Locale, FormatStyle, or a DateFormatter whose locale you have set, and prefer the user’s locale for a running app.
import Foundation
func displayPrice(_ amount: Decimal, currency: String, locale: Locale = .autoupdatingCurrent) -> String {
amount.formatted(
.currency(code: currency)
.locale(locale)
.precision(.fractionLength(2))
)
}
func displayDate(_ date: Date, locale: Locale = .autoupdatingCurrent) -> String {
date.formatted(
Date.FormatStyle(date: .abbreviated, time: .omitted)
.locale(locale)
)
}Points that catch UK apps out:
- The currency code comes from your product, the format from the locale. A UK store selling in GBP should pass
"GBP". Do not infer the currency from the device locale, or a user on holiday in the US will see a dollar sign on a pound price. (If you truly charge in the user’s local currency, that is a storefront decision, as with StoreKit’sdisplayPrice, not aLocaleguess.) - Time zones. A date-only event in Britain should be discussed in
Europe/London, including on the day clocks change. A timestamp for a server event should be stored in a precise form (often UTC) and converted for display. If you need to reason about a zone or a DST boundary, do it with a real zone identifier; the timezone and DST explorer is a useful check, but the app should useTimeZoneandLocale, not a hard-coded offset of+0or+1. - First day of the week and calendar. A week view should follow the locale; do not assume Monday everywhere, even if Monday is correct for
en-GB. - Units. If you show distances, decide whether you are metric, and be consistent. The UK is messy in speech (miles on the road, metres on a plan); pick one system per feature and label it.
Preview and screenshot locales should include en_GB and, if you ship it, en_US, so a 12-hour versus 24-hour time or a day/month swap is visible before release.
Images, layout and text that grows
Text in a bitmap is not localised when you add a language. If a marketing banner in the app says “Colour themes”, that is a second, hidden string table. Prefer text in the view, over the image, or use asset catalog locale variants when the image itself must change.
Layout needs to tolerate growth even if you only ship English today. UK strings are not always shorter than US ones (authorised representative is a long phrase), and if you add German or French later, buttons that were sized to “OK” will break. Use stacks, flexible widths and the same Dynamic Type discipline you should already have for accessibility. Do not set a fixed height on a label that can wrap.
Right-to-left languages are a bigger layout job if you add one; you do not have to solve Arabic on a UK-only app, but you should avoid assuming that a chevron “leading” is always on the left if you use raw left/right constraints. SwiftUI’s leading and trailing already help.
Watch for typography details that people notice in UK English: a typographic apostrophe in don't if you have chosen curly quotes, and a correct pound sign (£, U+00A3) rather than a lookalike. If a character looks wrong, inspect the code point; the Unicode inspector will tell you whether you pasted a similar symbol by mistake.
Pluralisation and strings with arguments
English still has plurals, and “1 orders” is as sloppy in en-GB as in en-US. String Catalogs support plural variants (zero, one, other — and other categories when you add languages that need them). Use them rather than count == 1 ? "…" : "…", which will be wrong as soon as a language has more forms, and is easy to get wrong for zero in English too.
let text = String(
localized: "order.count \(count)",
defaultValue: "\(count) orders"
)In the catalog, supply “1 order” for the one case and “%lld orders” (or the interpolation the catalog shows) for other, including zero if you want “0 orders” rather than a special empty state. Empty states are often a different string entirely (“No orders yet”), which is better than forcing a plural to do that job.
Arguments should be typed. A name, a date and a number in one sentence need a format the translator can reorder. Do not assemble sentences with +. Do not use a placeholder like TBD or dummy lorem in shipping strings; if you need temporary copy, the placeholder copy tool is for mocks, not for the catalog you ship.
Comments in the catalog should mention what each argument is. %lld without a comment is how a year becomes a count in a translation.
SwiftUI and String Catalogs
SwiftUI’s Text("Save") and Label("Account", systemImage:) participate in extraction. LocalizedStringKey is the type you already pass without thinking. A few sharp edges:
- String interpolation in
Textis extracted when it is a literal format. If you build aStringin code and pass it toText, you have left the catalog and the interpolation may not be localised or reorderable. PreferText("Hello, \(name)")orString(localized:). AttributedStringand markdownTextcan still be localised, but markdown is part of the string. Translators will need to keep the emphasis marks. Do not hide a name inside a markdown link you then refuse to put in the catalog.navigationTitle("Settings")is user-facing. So are tab titles, alert buttons and.accessibilityLabel. All of them belong in the catalog.- Previews should set a locale:
.environment(\.locale, Locale(identifier: "en_GB")), and a second preview foren_USif you ship it. That is the cheapest way to see a wrapping title and a date format.
If you inject copy for tests or for a design preview, inject a catalog-backed string, not a raw English literal you will forget to add later.
A testing routine that catches gaps
Localisation fails quietly: a key is missing, a string is still in the development language, a plural is wrong, a date is US-shaped on a UK screenshot. Build a routine, and run it before you ship a screen, not after a translator emails you.
- A test that the catalog compiles and that expected keys exist for every language you ship. If you use stable keys, this is a simple list. If you rely on source extraction, a test can load the bundle and resolve a sample of strings in
en-GBanden-US. - A run in each shipped language, with the scheme’s App Language and App Region set (English, United Kingdom; English, United States). Walk the main screens once.
- Pseudolanguage or a stretched preview if you expect more languages later, to catch truncation.
- Snapshot or screenshot tests for a couple of screens in
en-GBanden-US, especially where dates and prices appear. The Swift Testing guide covers the test runner; the snapshots can sit next to your other view tests. - A glossary grep for banned American forms in the
en-GBtable (color,favorite,canceled,ZIP) and the reverse inen-US. - A missing-string check in debug builds. Some teams log when a key falls back to itself in a non-development language. If you add French later, this is how you notice you shipped
Savein English on one button.
Do not treat a green build as proof. The compiler will not object to “Color themes” in an en-GB catalog.
Key takeaways
- Set the development language to
en-GBfor a UK-first app, and adden-USas a real table when you have US users, rather than shipping a genericenand hoping. - Keep copy in a String Catalog, with comments, stable keys where the English will change, and no dead strings.
- Localise the words that actually differ, and format dates, numbers and currency with
LocaleandFormatStyle. Do not infer a currency from the device region. - Keep text out of images where you can, and lay out for wrapping even if you only ship English today.
- Use catalog plurals and format arguments; never assemble sentences with
+. - Test in
en-GBanden-US, grep for glossary slips, and add a missing-key check before you add a third language.
If you want a second pair of eyes on a catalog or a UK/US glossary, tell me about it.
Frequently asked questions
Should a UK app use en or en-GB?
Use en-GB. Generic en is easy to treat as US English, and it makes a later en-US overlay messy. A UK-first app should develop in en-GB and add en-US when it needs it, rather than the other way around.
What is a String Catalog in Xcode?
A .xcstrings file that holds source strings, translations, comments and variations (including plurals) in one place. Xcode extracts strings from standard APIs such as String(localized:) and SwiftUI Text, and compiles the catalog into the bundles the app uses at runtime. It replaces most new use of Localizable.strings and .stringsdict.
How do you localise SwiftUI text?
Prefer literal Text("…"), Label, navigationTitle and String(localized:) so the catalog can extract the copy. Set a locale in previews. Do not build a finished String in code and pass it to Text if you want the interpolation to be translatable and reorderable.
How do you format dates and currency for the UK in iOS?
Use FormatStyle or a formatter with a Locale. For a running app, .autoupdatingCurrent is usually right for the format. Pass an explicit currency code ("GBP") for prices you charge in sterling, rather than guessing from the device region. Preview and test with en_GB so you see day/month order and the pound sign.
How do you test missing localisation strings?
Resolve a sample of keys in each shipped language and fail if they fall back unexpectedly, walk the app with the scheme’s language and region set, and grep catalogs for glossary mistakes. When you add a language, log missing keys in debug builds. A successful compile does not mean the en-GB table is complete or actually British.
Written by Lee Johnson. Published 10 November 2026.