jpskill.com
🛠️ 開発・MCP コミュニティ

localization-patterns

モバイルアプリの多言語対応に必要な、文字列リソース管理、複数形対応、右から左への表示サポート、Composeローカライズ、KMP共有文字列、ロケール管理などを効率的に行うためのパターンを提供するSkill。

📜 元の英語説明(参考)

Localization patterns for mobile - string resources, plurals, RTL support, Compose localization, KMP shared strings, and locale management.

🇯🇵 日本人クリエイター向け解説

一言でいうと

モバイルアプリの多言語対応に必要な、文字列リソース管理、複数形対応、右から左への表示サポート、Composeローカライズ、KMP共有文字列、ロケール管理などを効率的に行うためのパターンを提供するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o localization-patterns.zip https://jpskill.com/download/16424.zip && unzip -o localization-patterns.zip && rm localization-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/16424.zip -OutFile "$d\localization-patterns.zip"; Expand-Archive "$d\localization-patterns.zip" -DestinationPath $d -Force; ri "$d\localization-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して localization-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → localization-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

モバイル向けのローカライゼーションパターン

Android

strings.xml リソース

<!-- res/values/strings.xml (デフォルト - 英語) -->
<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 (スペイン語) -->
<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 (アラビア語 - 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>

引数を使用した文字列のフォーマット

// 位置引数: %1$s = 最初の文字列、%2$d = 2番目の整数
// strings.xml: <string name="greeting">Hello %1$s, you have %2$d new messages</string>

val formatted = context.getString(R.string.greeting, userName, messageCount)

// 複数形
val itemsText = context.resources.getQuantityString(
    R.plurals.items_count,
    count,  // quantity selector
    count   // format argument
)

Compose ローカライゼーション

@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))
    }
}

アプリごとの言語設定 (API 33+)

// 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>
// プログラムによる言語の変更
val localeManager = context.getSystemService(LocaleManager::class.java)
localeManager.applicationLocales = LocaleList(Locale.forLanguageTag("es"))

// API 33 より前の AppCompat の場合
AppCompatDelegate.setApplicationLocales(
    LocaleListCompat.forLanguageTags("es")
)

RTL レイアウトのサポート

<!-- AndroidManifest.xml -->
<application android:supportsRtl="true" ...>

<!-- left/right の代わりに start/end を使用 -->
<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 は RTL で自動的に反転
    ) {
        Icon(
            imageVector = if (isRtl) Icons.Default.ArrowBack else Icons.Default.ArrowForward,
            contentDescription = null
        )
        Text(text = stringResource(R.string.next))
    }
}

iOS

Localizable.strings

/* Localizable.strings (英語) */
"app_name" = "MyApp";
"welcome_message" = "Welcome, %@!";
"price_format" = "%@ %.2f";
"settings_title" = "Settings";
/* Localizable.strings (スペイン語) */
"app_name" = "MiApp";
"welcome_message" = "Bienvenido, %@!";
"settings_title" = "Configuración";

String Catalogs (.xcstrings - Xcode 15+)

Xcode 15 では、.strings および .stringsdict を置き換える単一の JSON ファイルとして、String Catalogs (.xcstrings) が導入されました。Xcode はコードからローカライズ可能な文字列を自動的に抽出し、翻訳を一元管理します。

String(localized:) と NSLocalizedString

// モダン (iOS 16+)
let welcome = String(localized: "welcome_message")

// 引数付き
let greeting = String(localized: "Welcome, \(userName)!")

// レガシー
let title = NSLocalizedString("settings_title", comment: "Title for settings screen")

SwiftUI Text と LocalizedStringKey

struct WelcomeView: View {
    let userName: String

    var body: some View {
        VStack {
            // 自動的に Localizable.strings を使用
            Text("settings_title")

            // 補間付き
            Text("Welcome, \(userName)!")

            // 明示的なローカライズされたキー
            Text(LocalizedStringKey("welcome_message"))
        }
    }
}

.stringsdict を使用した複数形ルール

<!-- 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)

SwiftUI での RTL サポート

str
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Localization Patterns for Mobile

Android

strings.xml Resources

<!-- 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>

String Formatting with Arguments

// 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
)

Compose Localization

@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))
    }
}

Per-App Language Preference (API 33+)

// 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")
)

RTL Layout Support

<!-- 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))
    }
}

iOS

Localizable.strings

/* 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";

String Catalogs (.xcstrings - Xcode 15+)

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.

String(localized:) and NSLocalizedString

// 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")

SwiftUI Text with LocalizedStringKey

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"))
        }
    }
}

Plural Rules with .stringsdict

<!-- 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)

RTL Support in SwiftUI

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)
    }
}

KMP Shared Strings

Common Interface with expect/actual

// 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)
    }
}

moko-resources Library Pattern

// 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()

Testing

Pseudo-Localization

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

Layout Testing with Long Strings

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
}