Skip to main content

Localize Apple and Google Wallet Passes Without Mixed-Language Updates

2026-10-12

Localize Apple and Google Wallet Passes Without Mixed-Language Updates

Wallet pass localization often fails after the app itself looks correct. A pass can install with translated labels but retain an English image, expose localization keys, or fall back to an unexpected language after an update. The cause is structural: Apple Wallet passes and Google Wallet objects have their own localization schemas, packaging rules, and fallback values. They do not inherit the app's current screen resources. This guide builds one source model, generates each platform artifact from it, and verifies the installed pass rather than trusting the input files.

Why wallet pass localization fails outside the app

A wallet pass is a delivered product artifact. It may remain on a device long after the issuing app session ends, and another system renders it. That changes the engineering boundary.

Apple packages pass content, images, and localization data together. Apple's Pass Design and Creation guide shows language specific .lproj directories containing localized strings and images. The current source creation documentation treats the pass source as a separate package that is prepared before signing and distribution.

Google Wallet uses API objects rather than Apple's package layout. Its LocalizedString schema stores a required default value and an array of translated values. Each value carries a BCP 47 language tag such as en-US, de, or de-AT.

Those models solve the same user problem with different storage rules. Copying the app's resolved display text into either artifact throws away the information needed for fallback, later updates, and audit. The release pipeline should start from semantic fields and approved translations, then generate platform output.

Define one platform-neutral source record

Use stable field identifiers that describe meaning, not wording. A loyalty pass might have fields called member_name, points_balance, tier_name, and support_label. Store locale variants beside those identifiers. Keep image identities separate because an image can change by locale even when its filename stays fixed on the target platform.

A compact source record can look like this:

{
  "pass_id": "loyalty.standard",
  "version": 18,
  "default_locale": "en-US",
  "supported_locales": ["en-US", "es-ES", "fr-FR"],
  "fields": {
    "points_label": {
      "en-US": "Points",
      "es-ES": "Puntos",
      "fr-FR": "Points"
    },
    "support_label": {
      "en-US": "Get help",
      "es-ES": "Obtener ayuda",
      "fr-FR": "Obtenir de l'aide"
    }
  },
  "images": {
    "strip": {
      "en-US": "assets/strip.en-US.png",
      "es-ES": "assets/strip.es-ES.png",
      "fr-FR": "assets/strip.fr-FR.png"
    }
  }
}

The version belongs to the localization source, not just the pass record. Record it in build output or issuance logs so support can identify which translation set produced a pass. Do not infer the locale from a translated phrase. Pass the selected locale as data.

Define locale mapping in the generator. Your app may use es, es-ES, or an internal code, while a source file or wallet API expects a BCP 47 tag. Reject an unmapped locale during generation. Silent conversion to the default locale produces a valid artifact in the wrong language, which is harder to detect than a failed build.

Generate the Apple Wallet package deterministically

For Apple Wallet, generate the complete directory tree before calculating the manifest and signing the pass. Include every localized file under its exact relative path. A minimal structure may include:

pass.json
icon.png
strip.png
en.lproj/pass.strings
en.lproj/strip.png
es.lproj/pass.strings
es.lproj/strip.png
fr.lproj/pass.strings
fr.lproj/strip.png

Keep stable lookup tokens in pass.json, then define their visible values in each locale's pass.strings. Avoid putting a translated sentence in the JSON and hoping a second process recognizes it. The token is the contract between the pass field and every localized string table.

Archive paths matter. A practitioner report about localization keys appearing on a pass traced the failure to incorrect paths in the generated manifest. The package contained localization files, but Windows separators had been written where the archive needed forward slashes. A preview of source folders would not have caught the final package defect.

Localized images need the same package-level test. In another practitioner report, text changed language while strip.png did not. Treat that as evidence of a reproducible failure class, not a guarantee about every device or pass type. Inspect the final archive, confirm that each localized image is present under the intended path, and install the signed pass on the target operating system.

Use these Apple build gates:

  1. Every supported locale has a pass.strings file.
  2. Every token referenced by a localizable pass field exists in the default locale.
  3. Optional translations may fall back only through an explicit policy.
  4. Localized images have the expected dimensions and semantic identity.
  5. The manifest lists every generated localization file with normalized archive paths.
  6. Signing happens after the manifest and all localized files are final.
  7. The signed package is unzipped in CI and compared with the expected file inventory.

The last check catches generators that validate a source directory but sign a different temporary directory.

Generate Google Wallet localized values from the same source

For each Google Wallet field that supports localization, emit one LocalizedString. The default value should come from the reviewed default locale. Emit translated values only for mapped, approved locales.

{
  "defaultValue": {
    "language": "en-US",
    "value": "Points"
  },
  "translatedValues": [
    {
      "language": "es-ES",
      "value": "Puntos"
    },
    {
      "language": "fr-FR",
      "value": "Points"
    }
  ]
}

Google documents that the default value is displayed when no suitable translated value exists. This makes the default part of the visible fallback contract, not filler for schema validation. Review it with the same care as every translation.

Do not emit duplicate tags, empty values, or both a generic and regional tag without a decision about precedence. Normalize tags before creating the payload, then fail generation if two internal locale codes collapse to the same output tag. For example, an internal fr and fr-FR mapping may be intentional, but the generator must not overwrite one unpredictably.

Keep the generated API payload as a build artifact with secrets and user data removed. A structured diff then shows whether an update changed copy, locale coverage, image references, or unrelated pass fields. This review is more reliable than comparing screenshots alone.

Make fallback a release decision

Fallback should preserve a usable pass without disguising missing launch content. Apply separate policies to required text, optional text, and images.

Content type Missing target translation Release action
Required identity or instruction Block generation Fix before issuance
Optional descriptive field Use reviewed default if policy allows Record the fallback
Image with embedded text Block or use a language neutral asset Never substitute an unreviewed locale image
Decorative image without text Use the shared asset Verify layout in every locale

A language neutral asset is often safer than an English image in a Spanish pass. Do not label an asset neutral merely because it has no sentence. Logos, date formats, currency symbols, gestures, and cultural references can still carry locale assumptions.

Record fallback events during generation. The report should name the pass version, field or image identity, requested locale, selected fallback, and policy that allowed it. Avoid logging pass holder data. This gives localization leads an actionable queue without exposing account details.

Handle updates without creating mixed versions

Generate new and updated passes through the same compiler. A separate update code path tends to patch visible fields while leaving images, default values, or locale arrays behind.

Use an immutable translation snapshot for each release. Resolve all fields and assets from that snapshot, generate the complete platform artifact, validate it, and only then make it eligible for delivery. If validation fails, retain the previously approved pass rather than combining old and new pieces.

For a copy correction, decide whether it applies to every issued locale or only one. Increment the localization source version either way. The platform payload can remain selective, but the internal record should explain why two locale variants now differ. This prevents a later full regeneration from restoring the old text.

A rollback should also regenerate a known approved snapshot. Do not attempt to reverse individual translated fields from memory. Keep the source version, generator version, locale map version, and asset checksums together so the same artifact can be reproduced.

Verify the rendered pass, not only the payload

Schema and package checks catch malformed output. They cannot prove which language the wallet displays or whether an image fits. Run a small device matrix for every release that changes the generator, locale map, fallback policy, or pass template.

Test at least the default locale, one language with longer labels, one non-Latin script when supported, and one device language for which the pass has no translation. For each case:

  1. Install a newly generated pass.
  2. Check every visible field and image on the front and detail views.
  3. Confirm dates, numbers, and currencies use the intended locale policy.
  4. Change the device language and reopen the pass.
  5. Deliver an update and confirm text and images come from one source version.
  6. Remove one optional translation in a test fixture and confirm the documented fallback.
  7. Corrupt an Apple localization path or duplicate a Google locale tag and confirm CI rejects it.

Capture screenshots as evidence, but keep the machine-readable inventory and payload diff. A screenshot can miss hidden fields, fallback values, and absent locale entries.

Common implementation mistakes

The first mistake is resolving strings in the app and sending only the current display text. That works for one session and discards every alternative language. Send semantic IDs and locale data to the pass generator.

The second is validating source files before packaging. Validate the signed Apple archive and the final Google API payload. Path normalization, omitted assets, and stale generated files appear after source validation.

The third is treating images as ordinary shared assets. When an image contains text, it needs a locale owner, review status, fallback rule, and release test.

The fourth is allowing the default locale to hide incomplete launches. Defaults keep a pass usable, but they should not turn a missing required translation into a successful release.

The fifth is testing only initial installation. Updates exercise a different operational path and can produce mixed source versions if the generator applies partial patches.

Start by exporting one existing pass into the platform-neutral source record above. Generate both platform outputs from that record, add the corrupt-path and missing-translation fixtures, then install and update the pass under two device languages before moving the rest of the catalog.

References