From everything-claude-code-mobile
Provides localization patterns for Android mobile apps including string resources, plurals, RTL support, Jetpack Compose, KMP shared strings, and locale management.
npx claudepluginhub ahmed3elshaer/everything-claude-code-mobile --plugin everything-claude-code-mobileThis skill uses the workspace's default tool permissions.
```xml
Guides iOS localization with Xcode String Catalogs for translations, pluralization, RTL layouts, and SwiftUI LocalizedStringKey patterns.
Implement, review, or improve iOS/macOS app localization using String Catalogs (.xcstrings), generated symbols, LocalizedStringKey/Resource, pluralization, FormatStyle, RTL layouts, and Dynamic Type.
Localizes iOS apps with String Catalogs (Xcode 15+), type-safe symbols (Xcode 26+), plurals, RTL layouts, locale formatting, and .strings migration.
Share bugs, ideas, or general feedback.
<!-- res/values/strings.xml (default - English) -->
<resources>
<string name="app_name">MyApp</string>
<string name="welcome_message">Welcome, %1$s!</string>
<string name="price_format">%1$s %2$.2f</string>
<string-array name="categories">
<item>Electronics</item>
<item>Clothing</item>
<item>Books</item>
</string-array>
<plurals name="items_count">
<item quantity="zero">No items</item>
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
</resources>
<!-- res/values-es/strings.xml (Spanish) -->
<resources>
<string name="app_name">MiApp</string>
<string name="welcome_message">Bienvenido, %1$s!</string>
<plurals name="items_count">
<item quantity="one">%d elemento</item>
<item quantity="other">%d elementos</item>
</plurals>
</resources>
<!-- res/values-ar/strings.xml (Arabic - RTL) -->
<resources>
<string name="welcome_message">%1$s ,مرحبًا</string>
<plurals name="items_count">
<item quantity="zero">لا عناصر</item>
<item quantity="one">عنصر واحد</item>
<item quantity="two">عنصران</item>
<item quantity="few">%d عناصر</item>
<item quantity="many">%d عنصرًا</item>
<item quantity="other">%d عنصر</item>
</plurals>
</resources>
// Positional arguments: %1$s = first string, %2$d = second integer
// strings.xml: <string name="greeting">Hello %1$s, you have %2$d new messages</string>
val formatted = context.getString(R.string.greeting, userName, messageCount)
// Plurals
val itemsText = context.resources.getQuantityString(
R.plurals.items_count,
count, // quantity selector
count // format argument
)
@Composable
fun WelcomeScreen(userName: String, itemCount: Int) {
Column {
Text(text = stringResource(R.string.welcome_message, userName))
Text(text = pluralStringResource(R.plurals.items_count, itemCount, itemCount))
}
}
// AndroidManifest.xml
<application android:localeConfig="@xml/locales_config" ...>
// res/xml/locales_config.xml
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="es" />
<locale android:name="ar" />
<locale android:name="ja" />
</locale-config>
// Programmatic language change
val localeManager = context.getSystemService(LocaleManager::class.java)
localeManager.applicationLocales = LocaleList(Locale.forLanguageTag("es"))
// For pre-API 33 with AppCompat
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags("es")
)
<!-- AndroidManifest.xml -->
<application android:supportsRtl="true" ...>
<!-- Use start/end instead of left/right -->
<TextView
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:paddingStart="12dp"
android:textAlignment="viewStart" />
// Compose RTL
@Composable
fun AdaptiveLayout() {
val layoutDirection = LocalLayoutDirection.current
val isRtl = layoutDirection == LayoutDirection.Rtl
Row(
modifier = Modifier.padding(start = 16.dp, end = 8.dp)
// start/end automatically flip in RTL
) {
Icon(
imageVector = if (isRtl) Icons.Default.ArrowBack else Icons.Default.ArrowForward,
contentDescription = null
)
Text(text = stringResource(R.string.next))
}
}
/* Localizable.strings (English) */
"app_name" = "MyApp";
"welcome_message" = "Welcome, %@!";
"price_format" = "%@ %.2f";
"settings_title" = "Settings";
/* Localizable.strings (Spanish) */
"app_name" = "MiApp";
"welcome_message" = "Bienvenido, %@!";
"settings_title" = "Configuración";
Xcode 15 introduces String Catalogs (.xcstrings) as a single JSON file replacing .strings and .stringsdict. Xcode auto-extracts localizable strings from code and manages translations in one place.
// Modern (iOS 16+)
let welcome = String(localized: "welcome_message")
// With arguments
let greeting = String(localized: "Welcome, \(userName)!")
// Legacy
let title = NSLocalizedString("settings_title", comment: "Title for settings screen")
struct WelcomeView: View {
let userName: String
var body: some View {
VStack {
// Automatically uses Localizable.strings
Text("settings_title")
// With interpolation
Text("Welcome, \(userName)!")
// Explicit localized key
Text(LocalizedStringKey("welcome_message"))
}
}
}
<!-- Localizable.stringsdict -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...>
<plist version="1.0">
<dict>
<key>items_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@count@</string>
<key>count</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>No items</string>
<key>one</key>
<string>%d item</string>
<key>other</key>
<string>%d items</string>
</dict>
</dict>
</dict>
</plist>
let text = String(format: NSLocalizedString("items_count", comment: ""), itemCount)
struct AdaptiveRow: View {
@Environment(\.layoutDirection) var layoutDirection
var body: some View {
HStack {
// leading/trailing automatically respect RTL
Text("Label")
Spacer()
Image(systemName: layoutDirection == .rightToLeft
? "chevron.left" : "chevron.right")
}
.padding(.leading, 16)
.padding(.trailing, 8)
}
}
// commonMain
expect class StringProvider {
fun getString(key: String): String
fun getString(key: String, vararg args: Any): String
fun getPluralString(key: String, quantity: Int, vararg args: Any): String
}
// androidMain
actual class StringProvider(private val context: Context) {
actual fun getString(key: String): String {
val resId = context.resources.getIdentifier(key, "string", context.packageName)
return if (resId != 0) context.getString(resId) else key
}
actual fun getString(key: String, vararg args: Any): String {
val resId = context.resources.getIdentifier(key, "string", context.packageName)
return if (resId != 0) context.getString(resId, *args) else key
}
actual fun getPluralString(key: String, quantity: Int, vararg args: Any): String {
val resId = context.resources.getIdentifier(key, "plurals", context.packageName)
return if (resId != 0) context.resources.getQuantityString(resId, quantity, *args) else key
}
}
// iosMain
actual class StringProvider {
actual fun getString(key: String): String {
return NSBundle.mainBundle.localizedStringForKey(key, value = key, table = null)
}
actual fun getString(key: String, vararg args: Any): String {
val template = getString(key)
return String.format(template, *args)
}
actual fun getPluralString(key: String, quantity: Int, vararg args: Any): String {
val template = getString(key)
return String.format(template, *args)
}
}
// build.gradle.kts
commonMain {
dependencies {
implementation("dev.icerock.moko:resources:0.23.0")
}
}
// commonMain/resources/MR/base/strings.xml
// commonMain/resources/MR/es/strings.xml
// Access: MR.strings.welcome_message.getString()
Android: Enable in Developer Options > "Force pseudo-locales" to test with accented characters and RTL wrapping.
# Force locale on Android emulator
adb shell settings put system user_locale "ar-SA"
adb shell am restart
Create values-en-rXA/strings.xml (pseudo-locale) to test text expansion. German and Finnish text is typically 30-40% longer than English.
// Compose UI test with locale
@Test
fun testLongTextLayout() {
composeTestRule.setContent {
CompositionLocalProvider(
LocalConfiguration provides Configuration().apply {
setLocale(Locale("de"))
}
) {
WelcomeScreen(userName = "Maximilian Schwarzenegger")
}
}
// Verify no text truncation or overflow
}