Slint で値によって単数形と複数形を使い分ける方法

遅くなりましたが、この記事は [Slint Advent Calendar 2024](https://qiita.com/advent-calendar/2024/slint) 一日目の記事です。

# 単数形と複数形

ものを数える際に、言葉によって語形が変化することがあります。

例えば、Qt では [以下のようにルールがまとめられ](https://doc.qt.io/qt-6/i18n-plural-rules.html)、これらに対応しています。

| Language | Rule 1 | Rule 2 | Rule 3 |
|----------|--------|--------|--------|
| English | n == 1 | otherwise | N/A |
| French | n < 2 | otherwise | N/A |
| Czech | n % 100 == 1 | n % 100 >= 2 && n % 100 <= 4 | otherwise |
| Irish | n == 1 | n == 2 | otherwise |
| Latvian | n % 10 == 1&& n % 100 != 11 | n != 0 | otherwise
| Lithuanian | n % 10 == 1&& n % 100 != 11 | n % 100 != 12 && n % 10 == 2 | otherwise |
| Macedonian | n % 10 == 1 | n % 10 == 2 | otherwise
| Polish | n == 1 | n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 > 20) | otherwise |
| Romanian | n == 1 | n == 0|| (n % 100 >= 1 && n % 100 <= 20) | otherwise |
| Russian | n % 10 == 1&& n % 100 != 11 | n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 > 20) | otherwise |
| Slovak | n == 1 | n >= 2 && n <= 4 | otherwise |
| Japanese | otherwise | N/A | N/A |

# Slint での対応

Slint では 1.8.0 現在、単数形か複数形かの使い分けが可能になっています。

翻訳のドキュメントに含まれる [Plurals](https://docs.slint.dev/latest/docs/slint/src/language/concepts/translations#plurals) というセクションに以下の記載があります。

> Use plural formatting when the translation of text involving a variable number of elements should change depending on whether there is a single element or multiple.
> 
> Given count and an expression that represents the count of something, form the plural with the | and % symbols like so:
> 
> @tr("I have {n} item" | "I have {n} items" % count).
> 
> Use {n} in the format string to access the expression after the %.
> 
> ```
> export component Example inherits Text {
>     in property <int> score;
>     in property <int> name;
>     text: @tr("Hello {0}, you have one point" | "Hello {0}, you have {n} point" % score, name);
> }
> ```

翻訳対象用の `@tr` を利用し、
```
@tr("単数形の場合" | "複数形の場合" % 数値);
```
という表現で、単数形と複数形を切り替えます。
文字列中の `{n}` は、与えられた数値に置き換えられます。

## 実際のコード

[SpinBox](https://docs.slint.dev/latest/docs/slint/src/language/widgets/spinbox) の値が単数の場合は `1 point`、複数の場合は `n points` と表示するプログラムを書いてみました。

```
import { SpinBox } from "std-widgets.slint";
export component Demo {
    VerticalLayout {
        alignment: start;
        spin-box := SpinBox {
            value: 0;
        }
        Text {
            text: @tr("{n} point" | "{n} points" % spin-box.value);
            font-size: 24px;
            horizontal-alignment: center;
        }
    }
}
```

これを、[SlintPad](https://slintpad.com/?snippet=import+%7B+SpinBox+%7D+from+%22std-widgets.slint%22%3B%0Aexport+component+Demo+%7B%0A++++VerticalLayout+%7B%0A++++++++alignment%3A+start%3B%0A++++++++spin-box+%3A%3D+SpinBox+%7B%0A++++++++++++value%3A+0%3B%0A++++++++%7D%0A++++++++Text+%7B%0A++++++++++++text%3A+%40tr%28%22%7Bn%7D+point%22+%7C+%22%7Bn%7D+points%22+%25+spin-box.value%29%3B%0A++++++++++++font-size%3A+24px%3B%0A++++++++++++horizontal-alignment%3A+center%3B%0A++++++++%7D%0A++++%7D%0A%7D%0A&style=native) 上で実行すると以下のようになります。

![slintpad-2024-12-02_00.10.38.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13867/1f3a78e4-29fa-8a5d-8ef4-d373d4760877.gif)

# ドキュメントのバグを見つけました

先ほど引用したサンプルコードですが、バグがあります。

> ```
> export component Example inherits Text {
>     in property <int> score;
>     in property <int> name;
>     text: @tr("Hello {0}, you have one point" | "Hello {0}, you have {n} point" % score, name);
> }
> ```

単数形の場合: `"Hello {0}, you have one point"`

複数形の場合: `"Hello {0}, you have {n} point"`

となっていますが、複数形の場合は point**s** となっているべきでしょう。

## 修正しました

以下の PR を作成し、本体に取り込まれました。次のバージョンでは修正されていることでしょう。

https://github.com/slint-ui/slint/pull/6600/files

# おわりに

今回は、Slint の翻訳における複数形の扱いを紹介しました。

最初に紹介した通り、言語に寄ってはもっと複雑なルールに対応する必要があり、それは今後ということになるでしょう。

日本語には単数形／複数形はないので気にする必要はありませんが、英語などで複数形の表記が必要になる際は是非ご利用ください。

明日は @hermit4 さんによる「[Slint-python (Alpha) を使ってみる](https://qiita.com/hermit4/items/1a2173772e558212c3a4)」です。お楽しみに！