はじめに
本田技研工業でiOSアプリ開発を担当している松野です。
SwiftUIでアプリを開発していると、多言語対応(ローカライゼーション)は避けて通れない課題です。特に、String Catalog
を使って文字列を管理しているケースは多いでしょう。
そんな中、「ローカライズした文章の一部だけ太字にしたい」「特定の単語に下線付きのリンクを設定したい」といった、部分的なスタイリングの要件が出てくることがあります。
例えば、以下のようなデザインを実現したいケースです。
(英)Japanese airlines are JAL and ANA.
今回は、このような「多言語対応」と「部分的なリッチテキスト化」を両立させるための実装方法について、いくつかのパターンを比較しながら解説します。
課題:単純な実装ではうまくいかない
まず、多くの人が最初に試すであろういくつかの実装パターンと、その問題点を見ていきます。
パターン1: Text
の連結
SwiftUIでは +
演算子で Text
を連結できます。これを使えばスタイルの適用は簡単です。
(
Text("日本の航空会社には")
+ Text(.init("[日本航空](https://www.jal.co.jp/jp/ja/)"))
.bold()
.underline()
+ Text("と")
+ Text(.init("[全日空](https://www.ana.co.jp/)"))
.bold()
.underline()
+ Text("があります")
)
.tint(Color.gray)
課題: この方法では英語対応 (Japanese airlines are JAL and ANA.
) しようとすると、語順が変わるため if/else
などで表示を切り替える必要があり、言語が増えるたびに複雑さが増していきます。これでは String Catalog
を使うことが少々厳しいです。
パターン2:String Catalog
と Markdown
次に、ローカライズを考慮して String Catalog
を使う方法を考えます。Text
は限定的な Markdown を解釈できるので、以下のように記述できます。
Text("jpAirlines", bundle: .module)
.tint(Color.gray)
{
"sourceLanguage" : "ja",
"strings" : {
"jpAirlines" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Japanese airlines are [JAL](https://www.jal.co.jp/jp/ja/) and [ANA](https://www.ana.co.jp/)."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "日本の航空会社には[JAL](https://www.jal.co.jp/jp/ja/)と[ANA](https://www.ana.co.jp/)があります"
}
}
}
}
}
}
課題: これでリンクにはなりますが、太字 や 下線 といった細かいスタイリングができません。Markdown記法 (**太字**
など) は Text
の中では解釈されないため、デザイナーの要求に応えられない可能性があります。
パターン3:NSAttributedString
と HTML
リッチな表現といえば NSAttributedString
です。HTML文字列をパースして表示するカスタムViewを作成する方法が考えられます。
import SwiftUI
// HTMLをパースして表示するカスタムView
public struct HTMLText: View {
@State private var attributedString: NSAttributedString?
// ... (中略) ...
// HTMLのaタグを検出し、下線やフォントなどのスタイルを適用する処理
// let linkAttributes: [NSAttributedString.Key: Any] = [
// .font: linkFont,
// .foregroundColor: UIColor(linkColor),
// .underlineColor: UIColor(linkColor),
// .underlineStyle: NSUnderlineStyle.single.rawValue
// ]
// といった形式でリンク部分のattributesを設定
}
このカスタムViewを使えば、String Catalog
側でHTMLタグを直接記述することで、デザイン要件を満たます。
HTMLText(String(localized: "jpAirlines", bundle: .module), linkColor: .gray)
{
"sourceLanguage" : "ja",
"strings" : {
"jpAirlines" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Japanese airlines are <a href=\"https://www.jal.co.jp/jp/ja/\">JAL</a> and <a href=\"https://www.ana.co.jp/\">ANA</a>."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "日本の航空会社には<a href=\"https://www.jal.co.jp/jp/ja/\">日本航空</a>と<a href=\"https://www.ana.co.jp/\">全日空</a>があります"
}
}
}
}
}
}
課題?: 実現は可能ですが、リンクにスタイルを適用するだけのために、HTMLパーサーを含むカスタムViewを自前で用意するのは、かなり大掛かりでメンテナンスコストも高くなります。
今回紹介するテクニック:LocalizedStringKey
の文字列補間を活用する
そこで登場するのが、Text
のイニシャライザが持つ 文字列補間(String Interpolation) の機能です。
公式ドキュメントにも記載があるように、Text
の文字列リテラル内では \()
構文を使って、数値や日付、そして Text
や Image
インスタンス を埋め込むことができます。
To create a localized string key from a string interpolation, use the
\()
string interpolation syntax. ... The interpolated types can include numeric values, Foundation types, and SwiftUIText
andImage
instances.
これを利用すると、String Catalog
にはプレースホルダー (%@
, %d
など) を持つテンプレート文字列を定義し、コード側でそのプレースホルダーにスタイルを適用した Text
を埋め込むことができます。
実装方法
1. String Catalog
の設定
文章のテンプレートをキーとし、%@
をプレースホルダーとして配置します。リンク部分の文字列 (URLofJAL
, URLofANA
) も個別のキーとして定義しておきます。
{
"sourceLanguage" : "ja",
"strings" : {
"jpAirlinesInterpolation %@ %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Japanese airlines are %1$@ and %2$@."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "日本の航空会社には%1$@と%2$@があります"
}
}
}
},
"URLofANA" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ANA](https://www.ana.co.jp/)"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "[全日空](https://www.ana.co.jp/)"
}
}
}
},
"URLofJAL" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "[JAL](https://www.jal.co.jp/jp/ja/)"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "[日本航空](https://www.jal.co.jp/jp/ja/)"
}
}
}
}
}
}
2. SwiftUIコードの実装
Text
のイニシャライザ内で、文字列補間 \()
を使って、ローカライズした Text
に .bold()
や .underline()
といったモディファイアを適用して埋め込みます。
Text(
// "jpAirlinesInterpolation %@ %@" というキーを呼び出し、
// \(...) の部分をプレースホルダーに挿入する
"jpAirlinesInterpolation \(Text("URLofJAL").bold().underline()) \(Text("URLofANA").bold().underline())",
bundle: .main
)
.tint(.gray)
これによりString Catalogで文章や翻訳を管理しつつ、SwiftUI標準の Text
だけで完結する実装が可能となります。
応用例:様々なスタイルを適用する
この方法はリンクだけでなく、部分的に色を変えたり、特定の単語だけカスタムフォントを適用したりといった様々なケースで役立ちます。
Text("SomeFontsText \(Text("Bold", bundle: .main).bold()) \(Text("Italic", bundle: .main).italic()) \(Text("Red", bundle: .main).foregroundColor(Color.red)) \(Text("Blue", bundle: .main).foregroundColor(Color.blue)) \(Text("HighPitch", bundle: .main).speechAdjustedPitch(1.0)) \(Text("LowPitch", bundle: .main).speechAdjustedPitch(-1.0))", bundle: .main)
例えば、「本アプリHogeHugaをご利用いただきありがとうございます」のように、サービス名にだけ特別なフォントを適用する、といった使い方もできるかなと思います。
注意点
1. リンクごとに色を変えたい場合は不向き
「日本航空は青」「全日空は赤」のように、リンクごとに異なる色を付けたい場合はどうでしょうか?
このケースでは、LocalizedStringKey
の文字列補間だけでは少し難しくなります。なぜなら、.tint()
モディファイアは View
全体に適用され、個別の Text
に適用しても View
を返してしまうため、+
演算子や文字列補間が使えなくなるからです。
このような、より複雑なスタイリング要件がある場合は、前述の パターン3:NSAttributedString
を使うアプローチが有効になります。NSAttributedString
であれば、範囲(Range)を指定して個別に属性(色、フォントなど)を細かく設定できるためです。
2. 実行時の挙動に気をつける必要がある
String Catalogではプレースホルダーの個数と引数で与えた個数が異なっていてもコンパイルエラーが生じません。
そのため、String Catalogが機能せずText内に記載した内容がそのまま描画されてしまったり、実装方法によっては実行時エラーが生じてしまいます。
まとめ
SwiftUIで多言語対応されたテキストの一部にスタイルを適用する方法を解説しました。
LocalizedStringKey
を使う方法、NSAttributedString
を使う方法、またそもそもswift-markdownなどの外部ライブラリを用いる方法など、今回の要件を実現する方法は色々あります。
個々のプロジェクトの方針に従って適切な手法を選んでみて下さい。
追記
WWDC2025にてAttributedStringの更なる活用方法が紹介されました。
これによって今回紹介した手法を使わずとも柔軟なデザイン変更ができるかもしれません。
調査が完了したら別途記事にしたいと思います。
-
筆者が直近で飛行機に搭乗し、記憶に残っていたJAL/ANAを例示に採用させて頂きました。特段こちらの企業名に意味はありません。 ↩