Pairs Global iOSでのクールな多言語管理

Pairs Global事業部 iOSエンジニアの遊佐です。この記事はeureka Native Advent Calendar 2017 — Qiitaの15日目の記事です。

前回は川端さんのエウレカという組織で、スクラムがチームに起こした変化についてでした。

グローバルサービスに欠かせない多言語対応

Pairsは日本以外にも台湾、韓国といった国々にサービスを展開しています。

そこで今回は、グローバルサービスにおいて欠かすことの出来ない多言語対応について、実際にPairs Global iOSではどのように多言語対応・管理をしているのかをお話したいと思います。

これまでの多言語管理

これまで、Pairs Global iOSチームでは以下のような方法で多言語対応・管理をしていました。

  • Masterとなるスプレッドシートで文言とそのKeyとなるものを管理(文言やKeyの追加や修正をここで行う)
  • シートから文言をLocalizable.stringsへimport
  • func NSLocalizedString()でKeyを元に各言語の文言を取得・表示

しかしこの方法だと問題となることがいくつかあります。

これまでの多言語管理での問題点

  • Localizable.stringsを各言語分ひとつずつ地道に修正する必要がある。対応言語が増えれば増えるほどストレスフル。
  • Keyをtypoしてしまう可能性がある。
  • 既存のLocalizable.stringsの文言とKeyを誤って削除してしまった場合、コンパイル自体は通るため、アプリを起動して文言を確認するまで誤ったことを検知出来ない。
  • 文言とKeyを管理するためのMaster(スプレッドシート)を個別で用意すると、差分管理がうまく出来ずに何が最新で正しいものなのかわからなくなることがある。

新しい多言語管理

こういった問題を解決するために、現在Pairs Global iOSチームでは以下のような方法で、多言語管理を行なっています。

  • 文言とそのKeyをJSON & Gitで管理
  • JSONからLocalizable.stringsを自動生成
  • 文言の取得をenumで呼び出すためのSwiftコードをJSONから自動生成

自動生成はSwiftでCLIを作って行なっています。このCLIはSwiftコードの自動生成ツールSwiftGenを参考に実装しました。

それでは、実際にJSONと自動生成による生成物の中身を見ていきましょう。

L10n.json

{
"pickup.today": {
"Base": "本日最佳精選",
"zh-Hant": "本日最佳精選",
"ko": "오늘의 추천"
},
"visitor.totalCount": {
"Base": "7天內有{{ count }}人造訪",
"zh-Hant": "7天內有{{ count }}人造訪",
"ko": " 7일간{{ count }}명이 방문했습니다."
},
}

こちらが実際にPairs Globalで使用されている多言語管理のMasterとなるJSONの一部です。

まず、JSONの外側でKeyを定義します。そこからネストしてBase, zh-Hant(繁体字), ko(韓国)それぞれの地域の翻訳文言を並列に並べていきます。

こちらを元にCLIを使って、コマンド一発で以下のLocalizable.stringsStrings.swiftを生成します。

  • l10n gen L10n.json ./Directory

Localizable.strings

// Base
"pickup.today" = "本日最佳精選";
"visitor.totalCount" = "7天內有{{ count }}人造訪";
-------------------------------------------------------
// zh-Hant
"pickup.today" = "本日最佳精選";
"visitor.totalCount" = "7天內有{{ count }}人造訪";
-------------------------------------------------------
// ko
"pickup.today" = "오늘의 추천";
"visitor.totalCount" = " 7일간{{ count }}명이 방문했습니다.";

Strings.swift

public enum L10n {
/// Base : 本日最佳精選
/// zh-Hant : 本日最佳精選
/// ko : 오늘의 추천
case pickupToday
/// Base : 7天內有{{ count }}人造訪
/// zh-Hant : 7天內有{{ count }}人造訪
/// ko : 7일간{{ count }}명이 방문했습니다.
case visitorTotalCount(count: String)
}
extension L10n: CustomStringConvertible {
public var description: String { return self.string }
public var string: String {
switch self {
case .pickupToday:
return L10n.tr(key: "pickup.today")
case .visitorTotalCount(let count):
return L10n.tr(key: "visitor.totalCount")
.asTemplate(args: [
"count" : count
])
}
}
}
Localizable.stringsは自動で生成されるため、直に触れる必要はなく、MasterのJSONのみをメンテナンスしていくだけです。Gitで管理しやすいので、差分管理も完璧です。
また、文言を取得する際はL10n.todayPickup.string, L10n. visitorTotalCount(count: "10").stringのように呼び出すことが可能となります。
Keyのtypoを考える必要はありません。また、文言とKeyのセットをMasterのJSONから誤って消してしまった場合はコンパイルエラーとなり、すぐに検知することが出来ます。
最後に
このように、面倒な多言語管理がコマンド一発でストレスフリーになり、多言語対応にまつわる問題点を解消することができました。
今回の記事が、少しでもグローバルアプリの多言語対応の参考になればと思います!
明日はPairs JP事業部の丹さんのお話です。お楽しみに!
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.