· 6 min read
WordPace in 28 languages: the App Store side of localisation
When I wrote about WordPace in August, the interface was localised into eleven languages while the speech recognizer already handled 45 presentation locales. Version 1.3 closes that gap with 28 interface languages and 27 App Store listings. Translating the 139 interface strings with a language model turned out to be the smallest part of the work, and it is the part most people mean when they say an app has been localised. The larger part is what the App Store expects per language: a listing with six text fields and their own character limits, keywords that work in that market, a set of screenshots in the right pixel size, and an upload that can be checked before it changes a live product page. Doing that once for two languages by hand is an afternoon. Doing it for 27, and again for every update, needs a process. This post is about what that process has to cover, and about the decisions that were not obvious before I started.
Where the list of languages comes from
The scope is not a list I chose. WordPace asks the operating system which locales its on-device recognizer supports, currently 45 on macOS 27, and every one of those has to be served by an interface language and an App Store listing. The two do not line up as neatly as one would hope. Regional variants collapse: Austrian, Swiss and German German share one interface and one listing, and Irish, Indian and South African English go to the British listing, following which language Apple shows on which storefront. Four recognized languages have no App Store localisation at all: Kashmiri, Maithili, Nepali and Cantonese get a translated interface, but their store page falls back to what the storefront shows by default. App Store Connect knows exactly one code for Urdu and one for Bangla, both offered on the India storefront, so the Indian recognizer locales map there. Chinese needs a script named explicitly, because the framework resolves a bare Cantonese code to Simplified Chinese. And one recognizer entry is not a language at all but a code-switching model for Indian English and Hindi, which gets neither a translation nor a listing.
None of this is hard once it is known, but it took several wrong turns to find out, and it is easy to break again by accident. So the mapping lives in one table with a note on every exception, and everything else is generated from it: the language list of the string catalog, the project's known regions, the folder per listing, the table the screenshot tests use. Even the locale codes are copied from Apple's published table rather than typed from memory, and a check fails when the copy disagrees with the locale list that fastlane ships. Either side may be wrong; the disagreement is the signal to look at Apple's page.
Translating against a brief
The interface translation is done by a language model, called headlessly with a schema for the answer, and I would not want to do it any other way for 26 languages. What made it reliable was not the model but two things around it. First, the instructions are a written brief, not a prompt typed on the spot, and every rule in it comes from something that went wrong in an early language. Format specifiers have to survive exactly, and a language that changes the word order has to switch to positional ones. Each language decides once how it abbreviates words per minute (WPM in German, PPM in Spanish and Italian, MPM in French, 語/分 in Japanese, 단어/분 in Korean) and every other string has to use that unit. Digits stay Western because the app draws its numbers that way. Standard menu items and the path to the microphone permission have to read exactly as macOS and iOS show them in that language. Plural forms follow the Unicode categories: Arabic needs six, Russian four, Japanese and Korean one. Second, the answer is checked structurally before it is merged: specifiers against the source, the unit against the language's own, the terms of a small glossary against the strings that define them, the plural categories against the language.
The instruction in the brief I consider the most useful is the last one: if the model cannot write a language to a native standard, it should say so instead of guessing. A flagged weak spot is information; a confident wrong sentence is not. For a human translator the same job can be written to a file and merged back through the same checks.
A listing is written for a market
App Store Connect wants six text fields per language: name and subtitle with 30 characters each, keywords with 100, promotional text with 170, description and release notes with 4000. The English listing is the factual basis, and a second brief lists what may be claimed and what may not. Keywords are the field where translation would be the wrong tool. The brief asks what someone in that market types into App Store search before a thesis defence or a wedding speech, in that script and in whatever Latin or English forms are common there. The Hindi keywords mix Devanagari and English, the Japanese ones include 卒論 (thesis) and 就活 (job hunting), terms no English list would contain. Two rules follow from how the store indexes: no space after a comma, and no word that already appears in the name or the subtitle, since those are indexed anyway and repeating them wastes the budget. Lengths are counted in UTF-16 code units, the most conservative of the plausible ways Apple counts. One market broke the rule that the name is always the untranslated product name: in Japan the bare name was already taken in App Store Connect, and the Japanese listing carries a suffix.
189 screenshots
Each listing gets five iPhone motifs and two Mac motifs, 189 images in total. The iPhone screens are captured by a UI test on a simulator set to the listing's language, the Mac screens by a capture mode in the app itself, since fastlane's screenshot tool has no working path for macOS. What is captured is the real interface in a staged state: the simulator ships no speech models, so a debug-only seed puts the app into a representative session with fixed readings. The artwork around the screens is rendered by a script that sets the headlines for each listing, and a third brief writes those headlines. The constraints there are visual: two lines of at most 14 characters for the title, because the renderer shrinks text that does not fit and a shrunk headline looks weak, and the register of consumer advertising in that market, which is why the German and Italian screenshots say du and tu although the app itself is formal.
Scripts other than Latin needed two changes in the renderer. The campaign typeface is monospaced and covers Latin only; in any other script the system substitutes glyph by glyph, and the monospaced spaces between substituted glyphs read as gaps, so those headlines are set in the system font, and Urdu in Nastaliq, which is what its readers expect. A right-to-left listing keeps the layout with the screen on the right, but its text aligns to the right edge of its column, and that edge has to stay clear of the screen. Before anything is uploaded, every file is checked for exact pixel size, 8-bit RGB, an sRGB tag and the absence of an alpha channel. The captures come with a fully opaque one, which App Store Connect rejects regardless.
Upload with a diff
The upload runs through fastlane, with two changes to how it works out of the box. A dry run reads the editable version from App Store Connect and prints, per listing and field, what a push would change; it sends nothing. The push shows the same diff, asks, and then uploads listings and screenshots. Neither step submits for review, uploads a binary or creates a version, and a push refuses to run unless the version is in a state that can be edited without pulling anything out of review. Screenshots are the part that needs attention afterwards: an uploaded asset can stay in processing indefinitely, so a status lane reads the state of every asset on both platforms, and a repair lane replaces the stalled ones from the local originals, waiting for the replacement to finish before it deletes the old asset.
What adding a language costs now
When an OS update teaches the recognizer a new locale, one command takes it through: it confirms with the API that the locale is really recognized, maps it, translates the interface, writes the listing and the headlines, regenerates the derived files and runs every check. Two more commands render the screenshots and show the diff against App Store Connect. The checks carry the process, 77 tests cover the tooling itself, and the output is short when everything holds:
OK: 45 recognition locales, 28 interface languages, 27 App Store listings, 139 strings.
OK: metadata for 27 listings.
What the checks cannot do is read. For the languages I do not speak, the structural checks are the only review the text gets: specifiers, units, glossary terms, lengths, plural forms. A wrong but well-formed sentence passes. That is the step I still want a native speaker for, and the export path for human translators exists for exactly that.