agentsclimarketplace

Localization rtl

Skill Emasoft/emasoft-complete-ios-app-authoring/skills/localization-rtl

Localize/internationalize apps (iOS 26, Swift 6, Xcode 26) and make them correct in RTL locales (Arabic, Hebrew, Persian). String Catalogs (.xcstrings), ICU plurals, locale-aware FormatStyle, runtime language switching, XLIFF, bidi text (FSI/PDI isolates, iOS 26 Natural Selection), RTL layout (leading/trailing, icon mirroring, sentinel method), RTL QA checklist. Use when localizing, adding a language, i18n/l10n, migrating Localizable.strings, currency/date formatting, supporting RTL, Arabic screens left-aligned, phone numbers reorder, bidi cursor jumps, translating xcstrings. Not for general SwiftUI layout (swiftui-ui).From its SKILL.md

Install
npx -y skills add Emasoft/emasoft-complete-ios-app-authoring --skill localization-rtl

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

17.3 KB, as published. Nobody here has run it

Localization & RTL for Apple Platforms

Authored against iOS 26.x / iPadOS 26.x / macOS 26.x / Swift 6.x / Xcode 26.x.

Two jobs hide inside "localize my app", and conflating them is the #1 mistake:

  1. Internationalization (i18n) — making strings & formats translatable. Pull every user-facing string into a String Catalog, never concatenate sentences, format numbers/dates/currency/lists through FormatStyle, and let plurals/gender/device variants live in the catalog.
  2. Right-to-left (RTL) correctness — making the layout mirror. SwiftUI mirrors automatically when the locale is RTL only if you wrote layout with leading/trailing (never left/right), mirrored directional icons, and handled bidirectional text at the right layer. "RTL is enabled" does not mean the screen is correct — verify visually.

Do both. A perfectly translated app with hardcoded .left alignment is broken in Arabic; a perfectly mirrored layout with "Hello, " + name is broken for every translator.

Before quoting an exact API signature (especially anything around cursor movement, UITextInput, or TextKit), fetch the current Apple docs (sosumi.ai/documentation/...) and verify against real code. The patterns below are starting points, not a substitute for the current reference.

Contents

Decision: which layer is the problem

SymptomLayerWhere to look
Text shows in English in a non-English localei18n — string not in catalog, or hardcodedString Catalogs
"1 items" / wrong plurali18n — plural not variedPluralization
Wrong number/date/currency format for regioni18n — manual formattingFormatting
Arabic text is left-alignedRTL — hardcoded .left, or root not RTLRTL layout / Sentinel
Back arrow / chevron points wrong way in ArabicRTL — image not mirroredRTL layout
Phone number / URL re-orders inside Arabic textbidi — missing isolate/markBidirectional text
Cursor "jumps" in mixed Arabic+Latin editingbidi — visual vs logical orderBidirectional text
Rows/accessories bunch to the middle in ArabicRTL — no flexible text zone / SpacerSentinel

Strings: String Catalogs (.xcstrings)

Use String Catalogs (.xcstrings, Xcode 15+) for everything — they replace Localizable.strings + .stringsdict, handle ICU plurals natively, and auto-extract Text(...) / String(localized:) keys on build. Prefer semantic keys (cart.checkout.cta) and always add a translator-facing comment:.

Never concatenate — interpolate

Word order differs across languages. Build complete sentences with placeholders; never glue translated fragments.

// WRONG — untranslatable: word order is fixed by code
Text("Hello, " + name + "!")
Text(String(localized: "items_count") + " \(count)")

// RIGHT — one catalog entry per sentence, placeholders re-orderable by locale
Text("greeting \(name)")            // catalog: "greeting %@" → "Hello, %@!"
Text("inbox.unread \(count)")       // catalog varies by plural (see below)

The implicit key of Text("greeting \(name)") is greeting %@; the translator sees the whole sentence and can move %@ wherever the grammar needs it.

Pluralization (ICU)

Use Vary by Plural catalog entries (Text("items.count \(count)")); never hand-write count == 1 ? "item" : "items" — plural category sets differ per language, and Arabic alone uses six (zero/one/two/few/many/other).

Locale-aware formatting

Numbers, currency, dates, percentages, lists, and measurements are not strings to translate — format through FormatStyle (.currency(code:), .percent, .number, .dateTime, .relative, .list, .measurement) so the locale's separators, digits, and order apply. Currency codes / ISO units never translate; force a specific currency only when the price is genuinely in it.

Runtime language switching

Per-scene (no restart, SwiftUI): inject .environment(\.locale, …). App-wide: persist the language code into AppleLanguages in UserDefaults (takes effect on next launch). When switching in-app, also set \.layoutDirection — a locale change alone does not reliably re-flip existing views.

RTL layout

SwiftUI mirrors automatically in RTL locales only if layout uses logical directions: .leading/.trailing and .natural flip; .left/.right never do (the classic "Arabic stays left-aligned" bug). UIKit: leadingAnchor/trailingAnchor, never leftAnchor. Directional icons need .flipsForRightToLeftLayoutDirection(true) or the .backward/.forward SF Symbol names. Preview with .environment(\.layoutDirection, .rightToLeft).

Bidirectional text (the deep part)

Stock text views handle bidi with .natural; most "bidi bugs" are a hardcoded .left alignment or a weak run (number/URL/phone) missing an isolate — wrap with FSI/PDI (U+2068/U+2069, preferred over LRM/RLM). Writing direction is controlled per layer (SwiftUI \.layoutDirection — Text ignores the .writingDirection attribute; NSParagraphStyle.baseWritingDirection; UITextInput; iOS 26 AttributedString.writingDirection). One logical caret position maps to two visual positions at a direction boundary, so cursor "jumps" are correct behavior. iOS 26 Natural Selection (selectedRanges) requires TextKit 2.

  • Full bidi guide (layer table, all control characters, cursor model, Natural Selection, macOS quirks): rtl-bidi-deep-dive.md

The visual sentinel method (RTL UI fixes)

When fixing an existing Arabic screen, mirror the visual result, not the code: verify the root direction first with a full-width Arabic sentinel Text (it must land on the visual right — if not, fix the root before any row fixes), then fix text width/alignment, row composition (text right, accessory left, Spacer between), directional icons, and compound-control internal order — then sweep every affected screen visually.

Testing localization & RTL

Scheme App Language / App Region overrides; Double-Length and Right-to-Left pseudolanguages (catch truncation and non-mirroring layout with no real translations); previews across Western/Asian/RTL/long-word locales; CLI simulator locale flip (xcrun simctl spawn … AppleLanguages) for cross-locale screenshots; per-locale snapshot tests; CI gate via references/scripts/xcstrings_validate.py validate.

Export / import for translators

xcodebuild -exportLocalizations / -importLocalizations (or Xcode Product → Export/Import Localizations…) round-trip per-language .xcloc bundles wrapping standard XLIFF for CAT tools.

Auto-filling translations

references/scripts/translate.py drafts missing/new/needs_review values in a .xcstrings catalog via an OpenAI-compatible endpoint — placeholder-preserving (%@, %lld, %1$@), brand/currency/URL-safe, with --dry-run, --locales, --force, --keep. A bootstrap for first-draft coverage, never a substitute for professional translation — run it, then export XLIFF for review.

Common mistakes

  1. Hardcoded .left / .right alignment. Absolute alignments never flip. Body text → .natural; layout → leading/trailing. Symptom: looks right in English, stays left-aligned in Arabic.
  2. String concatenation. "Hello, " + name is untranslatable. One catalog entry per full sentence with placeholders.
  3. Hand-rolled plurals (count == 1 ? "item" : "items"). Breaks for Arabic/Russian/Polish. Use plural-varied catalog entries.
  4. Manual number/date formatting ("\(count) items", String(format:) for currency). Use FormatStyle so separators/digits/order localize.
  5. Phone numbers / URLs reorder in RTL. Weak runs inherit surrounding direction — wrap with FSI/PDI (or LRM/RLM).
  6. Expecting linear cursor movement in bidi text. One logical position maps to two visual positions at boundaries; right-arrow need not move right. The bug is usually a custom UITextInput view, not the input.
  7. selectedRange (single) in bidi content. Visually disjoint across a boundary; adopt selectedRanges on iOS 26+, accept the artifact (or build a custom selection layer) below 26.
  8. TextKit 1 where Natural Selection / TK2 RTL geometry is needed. Any code path asking for layoutManager flips the view to TK1 and disables TK2-only behavior. Use textLayoutManager.
  9. SwiftUI Text + .writingDirection attribute. Silently ignored; SwiftUI direction flows through \.layoutDirection (or AttributedString.writingDirection on iOS 26+).
  10. A second local RTL override on a screen that already inherits RTL — the two fight; the screen looks half-mirrored. Determine the root first.
  11. Directional icons left LTR in an Arabic flow — back/forward arrows and chevrons need .flipsForRightToLeftLayoutDirection(true) (or the .backward/.forward SF Symbol). Conversely, non-directional icons must NOT mirror — battery, signal/Wi-Fi, clocks, logos, universal marks keep their look; custom UIImage/asset icons don't auto-flip even when they should. (Per-icon HIG rules in rtl-bidi-deep-dive.md.)
  12. In-app language switch that updates strings but not direction or derived content — set \.layoutDirection alongside \.locale, and refresh everything computed before the switch (startup-built tab/chart labels, scheduled notification copy, reminder strings in reducers, cached summaries, widget timelines). Appears only after a switch, never on fresh launch (full list in rtl-bidi-deep-dive.md pitfall 12).
  13. No translator context. Missing comment: produces wrong translations for ambiguous words ("Open" verb vs adjective). Always add comment:.
  14. Forcing Arabic / .rightToLeft at the app root to "fix Arabic" silently breaks English (Eastern-Arabic digits, Hijri dates, lost LTR). Derive locale/direction from the user's active-language choice; never hard-code either at root.
  15. Legacy DateFormatter()/NumberFormatter() with no explicit .locale follows the device locale, not the app's active language — set .locale for display, force en_US_POSIX for parsing server dates, and en for any formatter feeding a server/storage/arithmetic. (Details + the yyyy-vs-YYYY trap in localization-patterns.md.)

Visual RTL QA checklist

After changing shared components, check every affected screen visually — not just the component:

  • All user-visible strings come from Text / String(localized:); no hardcoded literals slipped through.
  • String Catalog is the single source of truth; no loose .strings for new modules.
  • Plurals use catalog variants; no string-built plurals.
  • Numbers, currency, dates, lists, measurements use FormatStyle.
  • Every key has a translator-facing comment:.
  • A full-width Arabic sentinel lands on the visual right.
  • Page title, subtitle/helper copy, card titles, card descriptions, row titles, row subtitles, metric labels/values — all read anchored to the Arabic side.
  • In rows with text + accessory: text is visually right, accessory visually left, with real empty space between.
  • Back arrows point right; disclosure chevrons sit opposite the main text; directional icons match the action. Non-directional icons (battery, signal/Wi-Fi, clock, logos) are not mirrored.
  • Compound controls (±/segmented/playback) are on the correct side and keep correct internal order (local LTR if needed).
  • Phone numbers / URLs / codes inside Arabic prose don't reorder (isolated).
  • No local RTL override fighting an already-inherited RTL root.
  • Double-Length and RTL pseudolanguage runs pass (no truncation, full mirror).
  • Direction-aware animations/gestures still feel correct (checked, not assumed).

References

  • references/localization-patterns.md — extended i18n reference: catalog JSON shape, manual-symbol-key extraction, type-safe L10n, plural/gender/device tables (+ Slavic one ≠ one), every FormatStyle, date parsing vs display (en_US_POSIX, yyyy-vs-YYYY), DateFormatter device-vs-app locale, SwiftUI gotchas (verbatim/LocalizedStringKey/bundle:.module), per-language expansion budget, runtime manager, accessibility strings, pseudo-localization, CLI simulator locale-flip, coverage + deterministic-audit tests, .strings migration, XLIFF.
  • references/rtl-bidi-deep-dive.md — full bidi reference: writing direction by every layer, visual-vs-logical model, all Unicode bidi controls, iOS 26 Natural Selection, alignment-in-RTL (+ HIG paragraph alignment, +2 pt visual balance, sequence-vs-digit-order, per-component mirroring), and 14 platform pitfalls (force-Arabic-at-root, variable-at-start flip, stale derived content, non-directional-icon mirroring, macOS NSTextView, TextKit-1 geometry).
  • references/builtin-script-fonts.md — on-device PostScript names for script-appropriate fallback faces (CJK PingFang/Hiragino/Apple SD Gothic, Arabic GeezaPro, Indic Kohinoor/Sangam), the .width(.condensed) no-asset fit-long-strings API, and the UIFont.familyNames discovery loop.
  • references/localization-templates.swift.md — drop-in Swift: L10n type-safe strings, @MainActor @Observable LocalizationManager (+ LanguagePickerView, .withLocalization()), and LocalizedPreview / RTLPreview / DynamicTypeLocalePreview / LocalePresets.
  • references/xcstrings-translator.md + references/scripts/translate.py — auto-fill missing .xcstrings translations (placeholder-safe, brand-safe, dry-run).
  • references/scripts/xcstrings_validate.py — deterministic, zero-network .xcstrings auditor (validate / audit-plurals / status / add-key / fix-plurals); CI gate counterpart to translate.py.
  • Apple: String Catalogs · Localization · FormatStyle · UITextInput · NSParagraphStyle · AttributedString.writingDirection · EnvironmentValues.layoutDirection

Keep looking

Skills are one crate of 626,194. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.