JUCEのLocalisedStringsについての覚え書き

1つのプログラムを多言語に対応させる「ローカライズ」は、ユーザーフレンドリーなアプリケーションを提供するにあたり重要な要素となっています。
JUCEでは翻訳リソースを準備、プログラムに反映するためのツールが備わっています。
本記事では、JUCEアプリケーションにおける翻訳リソースの準備とプログラムへの反映について解説します。

# `juce::LocalisedStrings`クラス

JUCEではローカライズエンジンとも言える機能が`juce::LocalisedStrings`クラスに実装されています。
[公式リファレンス](https://docs.juce.com/master/classLocalisedStrings.html)

# 1. プログラミング時、翻訳を反映したい文字列に`TRANS`マクロを付けておく

JUCEのローカライズエンジンでは`TRANS`マクロの引数に渡された文字列が翻訳対象の文字列になります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/66ce3c7a-c014-754b-201a-cf2504446fb6.png)


# 2. Projucerから『Translation File Builder』を起動する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/b4836e9e-66cc-351a-1dcf-41dee933982f.png)

↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/63208f52-32b1-767a-0a14-acd084131fb6.png)

# 3. Translation File Builderでソースコード内に記述された"TRANS"マクロ付きの文字列リストを取得する

Translation File Builderの[Scan folder for TRANS macros]をクリックしてソースコードが置かれたフォルダを選択します。
選択されたフォルダ内に置かれたソースコードから`TRANS`マクロで囲まれた文字列を検索し、翻訳対象の文字列リストを生成します。この時、検索した文字列ごとに一意の識別記号と共に表示されます。次の図では "JCTRIDX0."と書かれた文字列が識別記号です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/dc7cfc80-1a58-46ed-2f0d-6d937c18d89f.png)


# 4. Translation File Builderに翻訳後の文字列を入力する

Translation File Builderの2段目の欄に翻訳後の文字列を入力します。この時、ソースコード内の文字列ごとに設定された一意の識別記号を基準として翻訳対象の文字列が紐付けられることに気を付けましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/26ea9272-fb95-5810-dda4-376b8010744e.png)


# 5. Translation File Builderの[Generate]を実行する

"翻訳前の文字列" - "翻訳後の文字列"の対応リストが生成されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/312ef07f-00e0-2bdc-381c-f99234922d15.png)


# 6. UTF-8エンコーディングのテキストファイルを作成し、テキストファイルに生成された翻訳データをペーストする

JUCEではUTF-8エンコーディングの文字列を扱います。UTF-8エンコーディングのテキストファイルを作成し、
テキストファイルのエンコーディングがShift-JISだった場合、翻訳結果が文字化けする可能性があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/f2e5fdd7-2dd5-5651-2c33-2d3df29dd143.png)


# 7. ペーストしたテキストファイルのヘッダにある"language"と"countries"の項目を修正する

"language"項目について：`juce::LocalisedStrings`クラスに翻訳リソースをロードした際に`LocalisedStrings::getLanguageName()`関数からこの値を取得します。
"countries"項目について：`juce::LocalisedStrings`クラスに翻訳リソースをロードした際に`LocalisedStrings::getCountryCodes()`関数からこの値を取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/b1379d3b-20bb-3b66-b176-8b3232df9d55.png)

# 8. 翻訳リソース（テキストファイル）をプログラムから読み込む

テキストファイルをプログラムに読み込む方法は任意で構いません。
本記事での事例では、英語用と日本語用の翻訳リソース（テキストファイル）を用意してProjucerでバイナリデータ化してプログラムに組み込むことにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/28682684-79bd-2c39-3d6c-00dc178d46bc.png)

バイナリデータ化したテキストファイルは以下のJUCE APIでバイナリデータからString型のデータに変換することができます。

```
String::createStringFromData(BinaryData::jaJP_txt, BinaryData::jaJP_txtSize);
```

# 9. `juce::LocalisedStrings`のマッピングをセットする

`juce::String`型のデータから`juce::LocalisedStrings`型のインスタンスを作成し、`juce::LocalisedStrings::setCurrentMappings()`に渡すことで、`juce::LocalisedStrings`におけるマッピングデータを変更します。

`juce::LocalisedStrings`クラスはシングルトンパターンで実装されています。マッピングデータを変更して以降に`TRANS`マクロに文字列を渡す処理を通ることで翻訳処理（文字列の変換）が実行されます。

```
txt = String::createStringFromData(BinaryData::jaJP_txt, BinaryData::jaJP_txtSize);
LocalisedStrings::setCurrentMappings(new LocalisedStrings(txt, false));
```

# 本記事の実装例

#### ■ MainComponent.h
```
#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class MainComponent   : public Component
{
public:
    MainComponent();
    ~MainComponent();

    void paint (Graphics&) override;
    void resized() override;

private:
    ComboBox localeSelector;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
```

#### ■ MainComponent.cpp
```
#include "MainComponent.h"

MainComponent::MainComponent()
{
    setSize (600, 400);

    addAndMakeVisible(localeSelector);
    localeSelector.addItem("en", 1);
    localeSelector.addItem("ja", 2);
    localeSelector.onChange = [&] {
        String txt;
        if (localeSelector.getText() == "en"){
            getLookAndFeel().setDefaultSansSerifTypefaceName("Arial");
            txt = String::createStringFromData(BinaryData::enUS_txt, BinaryData::enUS_txtSize);
        }
        else if (localeSelector.getText() == "ja") {
            getLookAndFeel().setDefaultSansSerifTypefaceName("Meiryo UI");
            txt = String::createStringFromData(BinaryData::jaJP_txt, BinaryData::jaJP_txtSize);
        }
        LocalisedStrings::setCurrentMappings(new LocalisedStrings(txt, false));
        repaint();
    };
    localeSelector.setSelectedItemIndex(0);
}

MainComponent::~MainComponent()
{
}

void MainComponent::paint (Graphics& g)
{
    g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
    g.setFont (Font (16.0f));
    g.setColour (Colours::white);
    g.drawText (TRANS("Hello World!"), getLocalBounds(), Justification::centred, true);
}

void MainComponent::resized()
{
    auto bounds = getLocalBounds();
    localeSelector.setBounds(bounds.removeFromTop(30));
}

```

#### ■ スクリーンショット
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/71d40cde-1d4e-a483-05d3-c7b4c78d7e09.png)