27
Help us understand the problem. What are the problem?

posted at

updated at

[Unity]ローカライズ拡張例

com.unity.localization

Unity にて Localization というローカライズ用のパッケージがリリースされています。インストールは Package ManagerAdd package by name...com.unity.localization と入力 (Unity 2019.4 以降を推奨)。(公式ドキュメント
なお依存している Addressables パッケージもインストールされます。すでにこれを使用しているプロジェクトの場合はバージョン更新が必要になるかもしれません。

導入手順等々は省略し、本稿ではこのパッケージで可能なカスタムフォーマットの例を紹介します。

ローカル変数

まず準備として、今回のすべての例に使う、変数を導入します。グローバル変数とローカル変数の2種類が利用でき、今回はローカル変数で値を渡します。

通常の手順に従いテキストオブジェクトをローカライズ対応させると
Localize String Event
というコンポーネントが追加されるので、そこの Local Variables に値を追加します。
image.png

次にローカライズのエントリーを追加したら、 Smart チェックを入れます。

image.png

これで文字列は解釈されるようになり、 {変数名} で値に置き換えられます。

組み込まれている関数例

組み込まれてる関数でローカル変数を使用してみます。

image.png
英語に
{num:plural:an apple|{} apples}
と入力しておくと、ローカル変数を num としてゲーム上では
numが1 : an apple
numが2 : 2 apples
と表示され、厄介な複数形の問題を解決できます。

この plural という部分をカスタムで用意してみよう、というのがいうのが本稿の趣旨です。

カスタム関数の定義手順

1) C#で記述
2) それを登録

C#に加えて登録が必要なので、先に解説しておきます。カスタム関数を定義したら Project Settings から以下のように登録します。

image.png

この例では Fahrenheit Formatter という自作のフォーマッターを指定しています。以下の例すべてでこの登録が必要なので、忘れないようにしましょう。

実装例

摂氏華氏変換

まずはわかりやすい例。実際にゲームで使用される例は少ないように感じますが。

FahrenheitFormatter.cs
using System;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Assertions;

[Serializable]
[DisplayName("Fahrenheit Formatter")]
public class FahrenheitFormatter : FormatterBase
{
    public override string[] DefaultNames => new string[] { "fahrenheit" };

    public override bool TryEvaluateFormat(IFormattingInfo formattingInfo)
    {
        if (formattingInfo.CurrentValue is float celsius)
        {
            var f = celsius * 9f / 5f + 32f;
            var str = f.ToString(formattingInfo.FormatterOptions);
            formattingInfo.Write($"{str}");
            return true;
        }
        else
            Assert.IsTrue(false); // make sure the value is float.

        return false;
    }
}

翻訳例:
image.png

実行結果:
英語:
image.png
日本語
image.png

マイル速度変換

自動車のメーターで速度に mph 表記をする例は多いので、現実世界のレースゲームを作るのであれば必須でしょう。

MphFormatter.cs
using System;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Assertions;

[Serializable]
[DisplayName("MPH Formatter")]
public class MPHFormatter : FormatterBase
{
    public override string[] DefaultNames => new string[] { "mph" };

    public override bool TryEvaluateFormat(IFormattingInfo formattingInfo)
    {
        if (formattingInfo.CurrentValue is float kph)
        {
            var mph = kph * 0.621371f;
            var str = mph.ToString(formattingInfo.FormatterOptions);
            formattingInfo.Write($"{str}");
            return true;
        }
        else
            Assert.IsTrue(false); // make sure the value is float.

        return false;
    }
}

翻訳例:
image.png

実行結果:
英語:
image.png

日本語:
image.png

順位表示

1st, 2nd, 3rd, 4th ときて
11th, 12th, 13th となり
21st, 22nd, 23rd という、とても厄介な英語の問題。
人数の多いランキングを表示するときは対応が必要になります。

OrdinalFormatter.cs
using System;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Assertions;

[Serializable]
[DisplayName("Ordinal Formatter")]
public class OrdinalFormatter : FormatterBase
{
    public override string[] DefaultNames => new string[] { "ordinal" };

    public override bool TryEvaluateFormat(IFormattingInfo formattingInfo)
    {
        if (formattingInfo.CurrentValue is int num)
        {
            var absnum = Mathf.Abs(num);
            var j = absnum % 10;
            var k = absnum % 100;
            if (j == 1 && k != 11) {
                formattingInfo.Write($"{num}st");
                return true;
            }
            if (j == 2 && k != 12) {
                formattingInfo.Write($"{num}nd");
                return true;
            }
            if (j == 3 && k != 13) {
                formattingInfo.Write($"{num}rd");
                return true;
            }
            formattingInfo.Write($"{num}th");
            return true;
        }
        else
            Assert.IsTrue(false); // make sure the value is int.

        return false;
    }
}

翻訳例:
image.png

実行結果:
英語:
image.png

日本語:
image.png

漢数字

日本語に特化して頑張りたいケース。

KansuujiFormatter.cs
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Assertions;

[Serializable]
[DisplayName("Kansuuji Formatter")]
public class KansuujiFormatter : FormatterBase
{
    static readonly string[] Digit = { "", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
    static readonly string[] SubUnit = { "", "十", "百", "千" };
    static readonly string[] Unit = { "", "万", "億" };

    public override string[] DefaultNames => new string[] { "kansuuji" };
    StringBuilder _stringBuilder = new StringBuilder("", 128);

    void get4digits(int num, StringBuilder sb)
    {
        for (var i = 0; i < 4; ++i)
        {
            int r = num % 10;
            if (r > 0)
                sb.Insert(0, SubUnit[i]);
            if (r != 1 || i == 0)
                sb.Insert(0, Digit[r]);
            num /= 10;
        }
    }

    public override bool TryEvaluateFormat(IFormattingInfo formattingInfo)
    {
        if (formattingInfo.CurrentValue is int num)
        {
            if (num == 0)
            {
                formattingInfo.Write("零");
                return true;
            }

            _stringBuilder.Clear();
            var n = Mathf.Abs(num);

            for (var unit = 0; n > 0; n /= 10000)
            {
                var dig4 = n % 10000;
                if (dig4 > 0)
                {
                    _stringBuilder.Insert(0, Unit[unit]);
                    get4digits(dig4, _stringBuilder);
                }                    
                ++unit;
            }
            if (num < 0)
                _stringBuilder.Insert(0, "マイナス");
            formattingInfo.Write(_stringBuilder.ToString());
            return true;
        }
        else
            Assert.IsTrue(false); // make sure the value is int.

        return false;
    }
}

翻訳例:
image.png

実行結果:
英語:
image.png

日本語:
image.png

英語でカンマ区切りで表記されていますが、これは {chapter:#,0}. のように指定しているためです。通常の変数展開にはカスタム関数を記述しなくても、 C# の StringFormat のフォーマットが使用できます。

おまけ:ローカル変数の更新サンプル

ローカル変数をランタイムで更新する場合のサンプルを載せておきます。おなじゲームオブジェクトに追加し、ローカル変数名を指定すれば毎フレーム値が増加します。

LocalVariableUpdater.cs
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;
using UnityEngine.Localization.SmartFormat.PersistentVariables;
using UnityEngine.Assertions;

public class LocalVariableUpdater : MonoBehaviour
{
    public string LocalVariableName;
    FloatVariable _floatVariable;

    void Start()
    {
        var localizeStringEvent = GetComponent<LocalizeStringEvent>();
        var localizedString = localizeStringEvent.StringReference;
        _floatVariable = localizedString[LocalVariableName] as FloatVariable;
        Assert.IsNotNull(_floatVariable);
    }

    void Update()
    {
        _floatVariable.Value++;
    }
}

まとめ

そもそも単位変換は式さえあればいかようにも実装できますが、翻訳者が適切な単位を選択できるようにするところにローカライズでこれを行うメリットがあるでしょう。適切な表記というのは母国語の人間でないとわからないので、翻訳者に選択を委譲することでローカライズの品質を向上することができますね。めでたしめでたし。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
27
Help us understand the problem. What are the problem?