An app can switch every string to French while an onboarding diagram keeps its English labels. The same bug appears when a help image comes from a stale cache, an iOS extension cannot reach the main app's asset, or Android combines language and density qualifiers differently than expected. Replacing the image on one screen only hides the underlying resource problem. Localized images need stable semantic IDs, explicit fallbacks, accessible labels, and package-level release checks.
First decide whether the image should contain text
Before creating a file for each language, decide whether the words belong in the pixels. A diagram made of shapes and icons can reuse one illustration while the app renders localized labels on top. Unlike baked-in lettering, live text can respond to dynamic type, screen readers, right-to-left layout, copy edits, and languages added after the artwork was exported.
Keep text inside an image only when the pixels themselves are the product requirement. Examples include a screenshot of another interface, a regulated document sample, a chart supplied by a partner, or artwork whose lettering cannot be reproduced by the app layout. Even then, question whether an annotation layer can carry the translatable parts.
Use this decision rule:
- If the words identify controls or explain steps, render them as interface text.
- If the image demonstrates a third-party screen, localize the captured screen or clearly label the shown language.
- If the artwork is decorative, remove embedded words and provide one neutral asset.
- If text must stay in the pixels, register the image in the localization inventory before design handoff.
String review cannot detect text trapped in PNG, JPEG, PDF, or vector exports. Apple's Xcode localization overview includes strings, images, and other resource types in the internationalization surface. Put image ownership in the localization plan rather than leaving it in an informal design folder.
Give each image one semantic identity
Do not encode the current language into every call site. A view should ask for checkout-help-step-2, not choose between checkout-help-step-2-fr.png and checkout-help-step-2-en.png. The resource layer resolves the semantic ID against the effective locale and configuration.
A small manifest makes that contract reviewable:
{
"id": "checkout-help-step-2",
"owner": "checkout",
"default": "checkout-help-step-2-neutral",
"contains_text": true,
"accessibility_key": "checkout.help.step2.image_description",
"variants": {
"en": "checkout-help-step-2-en-v4",
"fr": "checkout-help-step-2-fr-v4",
"ja": "checkout-help-step-2-ja-v3"
}
}
The semantic ID stays stable when a designer exports a new revision. The versioned file identity changes when pixels change. This separation prevents cache collisions and lets release tooling compare expected variants without parsing file names scattered across the codebase.
The manifest also records whether embedded text exists. A build check can require locale parity only for text-bearing assets while allowing neutral icons and photographs to remain shared. That avoids copying identical files into every locale merely to satisfy a blunt file-count rule.
Use platform resource selection instead of screen logic
iOS and Android can both select localized resources, but each packages and resolves them differently. The semantic contract can stay shared while the screen uses its platform's resource system.
Resolve localized assets on iOS
Xcode treats localized images and catalog assets as project resources. The Xcode localization documentation groups resources and assets into the localization workflow and points to asset-catalog localization as a dedicated operation. Add every supported locale to the relevant asset or resource, then load the resource by its stable name from the bundle that owns it.
An image in the app target is not automatically available to a widget, App Clip, framework, or Swift package. Resolve the asset through its owning bundle instead of assuming the main bundle, record that owner in the manifest, and test each target independently.
A long-running screen also needs a refresh boundary. If the app supports changing language without restarting, image lookup must run again when the effective locale changes. Do not retain a resolved UIImage or SwiftUI image in a singleton whose key contains only the semantic asset ID. Include the locale and asset revision in the cache key, or clear localized image entries when the locale changes.
Two practitioner reports show where iOS asset lookup goes wrong. One asks how to localize images stored in an iOS asset catalog. The other describes French resources still displaying the English image after localized file groups were added. These reports do not establish a universal platform defect. They do justify tests for catalog ownership and runtime resource resolution.
Resolve localized drawables on Android
Android's resource system selects alternatives using configuration qualifiers. The official guide to providing alternative resources documents language and region directories, density variants, qualifier ordering, and default-resource behavior. Give each variant the same resource name so the view requests one R.drawable identity while Android chooses the best match.
For example:
res/drawable/checkout_help_step_2.png
res/drawable-fr/checkout_help_step_2.png
res/drawable-ja/checkout_help_step_2.png
res/drawable-ja-night/checkout_help_step_2.png
The unqualified file is the fallback. It must be safe to show when Android cannot match a locale-specific resource. For an image with embedded English text, that may be a poor fallback. Prefer a language-neutral image or a deliberately approved default rather than assuming English is acceptable in every failure case.
Qualifiers compose. A locale variant can still need density, night-mode, layout-direction, or device-specific alternatives. Avoid creating a partial matrix accidentally. If French has a localized normal-mode image but no dark-mode equivalent, verify what Android actually selects under fr plus night mode. The resource algorithm chooses the best available match, not the variant a file-name convention outside res/ implies.
Keep accessibility text out of the pixels
Accessibility behavior belongs in the same asset record. Exclude decorative images from the accessibility tree. Store concise descriptions for informative images as normal string resources. For an interactive image, label the action rather than its appearance.
Do not assume optical character recognition will expose embedded labels. Screen readers consume accessibility metadata supplied by the app. Link the manifest's accessibility_key to the same semantic asset identity, then validate that every text-bearing or informative variant has an approved description.
The description may differ by locale even when the underlying picture is neutral. It may also need to change when the asset revision changes. A screenshot that gains a warning banner invalidates an old description even if its file name remains stable. Tie image and accessibility review together in the same pull request.
For right-to-left locales, inspect more than the text. Directional arrows, progress sequences, navigation gestures, and spatial instructions may need a mirrored or separately designed asset. Logos, media controls, clocks, and other direction-independent symbols generally should not be mirrored. Record that choice per asset rather than applying one global transform to every localized image.
Version remote images and invalidate caches by locale
Apps often download onboarding art, campaign cards, or help diagrams from a content service. If the response varies by language, /images/checkout-help.png is an unsafe cache identity. A CDN or client cache may serve whichever language first occupied that URL.
Use a locale and immutable revision in the identity:
/images/checkout-help-step-2/fr/v4.png
The content record should return the resolved language as data. If French falls back to a neutral asset, the response can say resolved_locale: und rather than pretending French artwork exists. The client then caches by semantic ID, resolved locale, revision, appearance, and scale class.
Do not silently combine remote and bundled variants with different semantics. Define the order once:
- Use the exact remote locale variant for the current revision when verified.
- Use the language-only remote variant if the product permits regional fallback.
- Use the bundled neutral fallback.
- Hide the image only when the screen remains understandable without it.
Never leave a broken placeholder where the image carries required instructions. If no safe fallback exists, block the locale's release for that screen or redesign the instruction as live text.
Add image parity to the localization release gate
A release check should compare the manifest with the packaged output, not merely confirm that source files exist. Build every supported target and inspect the resources that actually ship.
For each text-bearing image, assert:
- The semantic ID has an approved default.
- Every launch locale has an exact variant or a documented fallback decision.
- The file revision matches the reviewed accessibility description.
- iOS target membership includes every bundle that loads the asset.
- Android qualifier directories use valid names and ordering.
- Density and appearance variants do not erase the locale match.
- Remote URLs include locale and immutable revision information.
- Runtime language changes invalidate resolved image caches.
Visual regression tests should capture the screen, not just the isolated file. The rendered result reveals wrong-language selection, clipping around overlaid labels, contrast failures, and mismatches between dark-mode artwork and the surrounding theme. Run the test on a clean install and after changing the app language because those paths can exercise different caches.
Use at least one negative fixture. Remove a locale variant in a test build and verify that the approved fallback appears. Point a remote response at an unknown revision and verify that the app keeps the bundled fallback. Move an iOS asset out of a test extension and confirm that the release check catches the missing owner before runtime.
Diagnose wrong-language images by layer
When the wrong image appears, inspect the resolved contract instead of swapping files until the screen changes:
- Log the effective app locale and the content locale separately.
- Log the semantic asset ID, owning bundle or Android resource ID, resolved variant, and revision.
- Confirm that the selected file exists in the built package, not only in the source tree.
- Clear or inspect client and CDN caches using the full locale-aware key.
- Test the same screen after cold start and after a runtime language change.
- Verify the accessibility description and directional treatment alongside the pixels.
The result will identify a missing translation, a packaging error, a bad fallback, a stale cache, or a lifecycle bug. Re-exporting an image cannot fix the wrong bundle lookup. Clearing a cache cannot supply a missing default resource.
Verify one asset end to end
Choose one text-bearing onboarding or help image in the next release. Give it a semantic ID, neutral fallback decision, locale variants, accessibility key, owner, and revision. Package it on iOS and Android, switch languages at runtime, test a missing variant, and inspect the built artifacts. Once that path passes, automate the same manifest checks for every localized image. That turns image localization from a design handoff into a release contract.
References
- Apple Xcode localization supports the project-level workflow for localizing images, assets, and other resources.
- Android alternative resources supports language qualifiers, default fallbacks, drawable variants, and resource selection.
- Stack Overflow: localizing images in an iOS asset catalog supplies practitioner evidence about catalog-managed image localization.
- Stack Overflow: localized iOS images still show English supplies practitioner evidence about runtime selection of localized file resources.
