SwiftUI の Text で多言語変数展開

## はじめに

SwiftUI で多言語対応をする際、以下のように表示したいとします

英語

```text
Apples are 1,000 yen each.
```

日本語

```text
りんごは一つ1,000円です
```

金額が固定であれば View 側で LocalizedStringKey を指定し、 各Stringsファイルに対応する文言を書いておけば問題ありません

Swift

```swift
Text("apple")
```

英語

```text
"apple" = "Apples are 1,000 yen each.";
```

日本語

```text
"apple" = "りんごは一つ1,000円です";
```

しかし、実際には金額が変動する場合、どうすべきでしょうか

## 文字列

まず先に、変数が文字列の場合を見てみましょう

英語

```text
Hello World!!
```

日本語

```text
こんにちは世界!!
```

`let target = "World"` とすると、例えばこのようになります

- Swift

  ```swift
  Text("hello\(target)")
  ```

- 英語

  ```text
  "hello%@" = "Hello %@!!";
  ```

- 日本語

  ```text
  "hello%@" = "こんにちは%@!!";
  ```

プレビュー結果（[多言語プレビューモジュール](https://qiita.com/RyoWakabayashi/items/81b34d47a1869115b07c)を使用）

![スクリーンショット 2021-07-19 18.56.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/ac2b8dd0-56dc-986b-9331-bc84f2cfa2d6.png)
![スクリーンショット 2021-07-19 18.57.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7323d6ca-6244-fbc4-8587-812f798c1e78.png)

Strings ファイルの方で `%@` としていた部分に `\(target)` とすることで、変数が展開されました

ただし、当然この方法では `World` は翻訳されません

翻訳したければ、 `World` の方も Strings ファイルに定義しておかねばなりません

また、 `NSLocalizedString` で先に翻訳してから埋め込むことになります

- Swift

  ```swift
  Text("hello\(NSLocalizedString(target, comment: ""))")
  ```

- 英語

  ```text
  "hello%@" = "Hello %@!!";
  "world" = "World";
  ```

- 日本語

  ```text
  "hello%@" = "こんにちは%@!!";
  "world" = "世界";
  ```

ただし、 `NSLocalizedString` の引数に変数を渡す行為は swiftlint にエラーを出されるため、おすすめできません

```text
TestView.swift:8:45: error: NSLocalizedString Key Violation: Static strings should be used as key/comment in NSLocalizedString in order for genstrings to work. (nslocalizedstring_key)
```

また、実機で動かしたときは問題ありませんが、プレビュー上は日本語の方も `こんにちはWorld!!` になってしまいます

以下のようにすれば、あまりキレイではありませんが、 swiftlint もエラーにならず、プレビューも機能します

- Swift

  ```swift
  Text("hello") + Text(LocalizedStringKey(target)) + Text("!!")
  ```

- 英語

  ```text
  "hello" = "Hello ";
  "world" = "World";
  ```

- 日本語

  ```text
  "hello" = "こんにちは";
  "world" = "世界";
  ```

## 整数

整数の場合、文字列と同じようにしてもうまくいきません

- Swift

  ```swift
  Text("price\(price)")
  ```

- 英語

  ```text
  "price%@" = "Apples are %@ yen each.";
  ```

- 日本語

  ```text
  "price%@" = "りんごは一つ%@円です";
  ```

これだと翻訳されないまま展開されるだけです

![スクリーンショット 2021-07-19 19.29.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d7dcd503-3807-7e27-4c5e-ed42207cafb4.png)

`%@` はオブジェクト（文字列含む）を指定するのに使われます

整数の場合は型の桁数に応じて指定が異なります（互換性はありません）

- Int 、 Int64 の場合、 `%lld` で指定します
- Int8 、 Int16 、 Int32 の場合、 `%d` を指定します

したがって、 `let price = 1000` というように Int 型を使っている場合は Strings ファイルを以下のように変更します

- 英語

  ```text
  "price%lld" = "Apples are %lld yen each.";
  ```

- 日本語

  ```text
  "price%lld" = "りんごは一つ%lld円です";
  ```

![スクリーンショット 2021-07-19 20.13.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f7845b72-6003-3c78-4c94-eeefc05e1455.png)
![スクリーンショット 2021-07-19 20.13.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a4e4bd90-6c83-9177-65a2-76e26cf8c575.png)

また、もう一つの方法として、文字列にしておいてから展開する方法もあります

この方法だと `%d` でも `%lld` でも問題ありません

- Swift

  ```swift
  Text("price\(String(format: "%lld", price))")
  ```

- 英語

  ```text
  "price%@" = "Apples are %@ yen each.";
  ```

- 日本語

  ```text
  "price%@" = "りんごは一つ%@円です";
  ```

ただし、カンマ区切りはしてくれないので、おすすめしません

![スクリーンショット 2021-07-19 19.52.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e2c77482-1667-5dc8-63dc-06fad3350f45.png)
![スクリーンショット 2021-07-19 20.11.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/dfef3a92-2427-f20c-996c-6bcb0880279b.png)

## 小数

小数の場合も型の桁数に応じて指定が異なります

また、展開先（ Strings ファイルの右辺）で桁数を指定します

- CGFloat 、 Float64 、 Double の場合、 `%lf` を指定します
- Float 、 Float32 の場合、 `%f` を指定します

実装例は以下のようになります

- Swift

  ```swift
  Text("pi\(CGFloat.pi)")
  ```

- 英語

  ```text
  "pi%lf" = "Pi: %.2lf";
  ```

- 日本語

  ```text
  "pi%lf" = "円周率: %.2lf";
  ```

![スクリーンショット 2021-07-19 19.57.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/394f2068-659d-6b34-e1ab-3bf97f823055.png)
![スクリーンショット 2021-07-19 19.57.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1c421dd9-8c3e-db53-ae1d-6155f386f43f.png)

## まとめ

Strings ファイル内で以下の指定子を使うことで翻訳しつつ変数を展開することができます

型の桁数で変わることに注意しましょう

|型|指定子|
|:--|:--|
|String|%@|
|Int|%lld|
|Int8|%d|
|Int16|%d|
|Int32|%d|
|Int64|%lld|
|Float|%f|
|Float32|%f|
|Float64|%lf|
|CGFloat|%lf|
|Double|%lf|