しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

UWP の Prism × Unity で C# コードから多言語化対応リソースを取得したい

Windows ランタイムアプリからはプロジェクト内に多言語化リソースを用意することで多言語対応がやりやすくなっています

f:id:matatabi_ux:20150905200522p:plain

上記の Strings フォルダ配下に配置した en-US/Resources.reswja-JP/Resources.resw がその多言語化リソースになります

フォルダ名の en-US や ja-JP は言語と地域を合わせたロケールを表すコードで、OS の地域と言語の設定により実際に参照されるリソースが自動的に切り替わる仕組みになっています


f:id:matatabi_ux:20150905203908p:plain

Resource.resw の中身は上記のような感じで記載した場合、画面に表示するには XAML を下記のように記述します

<TextBlock x:Uid="Description_EditLogo"/>

x:Uid *1 にキー名を入力すると、Text プロパティには Resources.resw 内に登録されている {キー名}.Text が暗黙的に設定されるというわけです

通常はこのやり方で問題ないと思いますが、ViewModel のプロパティに多言語化リソースの値を指定したいというときには XAML ではなくC# コードベースで参照する必要があります・・・これをどうするか

今回は UWP で最新の Prism × Unity ライブラリを利用した場合限定ですが C# コードから多言語化対応リソースを取得する方法を試してみました

まずは App.xaml.cs の初期化処理を修正

/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : PrismUnityApplication
{
    ~ 中略 ~

    /// <summary>
    /// Initialize application event handler
    /// </summary>
    /// <param name="args">Activated event arguments</param>
    /// <returns>Task</returns>
    protected override Task OnInitializeAsync(IActivatedEventArgs args)
    {
        // Registration service instance to Unity container
        this.Container.RegisterInstance<IResourceLoader>(new ResourceLoaderAdapter(new ResourceLoader()), new ContainerControlledLifetimeManager());
        this.Container.RegisterInstance<INavigationService>(this.NavigationService, new ContainerControlledLifetimeManager());

        // Customize title bar colors
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        titleBar.BackgroundColor = Colors.Black;
        titleBar.ForegroundColor = Colors.White;
        titleBar.InactiveBackgroundColor = Colors.Black;
        titleBar.InactiveForegroundColor = Color.FromArgb(0xff, 0x66, 0x66, 0x66);
        titleBar.ButtonBackgroundColor = Colors.Black;
        titleBar.ButtonForegroundColor = Colors.White;
        titleBar.ButtonInactiveBackgroundColor = Colors.Black;
        titleBar.ButtonInactiveForegroundColor = Color.FromArgb(0xff, 0x66, 0x66, 0x66);
        titleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xff, 0x17, 0x17, 0x17);
        titleBar.ButtonHoverForegroundColor = Colors.White;
        titleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xff, 0x34, 0x34, 0x34);
        titleBar.ButtonPressedForegroundColor = Colors.White;

        return base.OnInitializeAsync(args);
    }

    ~ 中略 ~
}

変更したのは次の一行

this.Container.RegisterInstance<IResourceLoader>(new ResourceLoaderAdapter(new ResourceLoader()), new ContainerControlledLifetimeManager());

UnityContainer に IResourceLoader のインタフェースで ResourceLoaderAdapter のインスタンスを登録しました・・・Unity に頼りまくりの書き方です

その後 ViewModel 側を下記のように記述

/// <summary>
/// TopPage ViewModel
/// </summary>
public partial class TopPageViewModel : ViewModel
{
    /// <summary>
    /// Multi languages resource loader
    /// </summary>
    private IResourceLoader resource = null;

    /// <summary>
    /// Constructor
    /// </summary>
    public TopPageViewModel()
    {
        this.resource = ServiceLocator.Current.GetInstance<IResourceLoader>();
        this.MenuItems = new ObservableCollection<MenuItemViewModel>
        {
            new MenuItemViewModel
            {
                DisplayName = this.resource.GetString("Menu_EditLogo/Text"),
                IconText = ((char)0xE71D).ToString(),
                Description = this.resource.GetString("Description_EditLogo/Text"),
                IsSelected = true,
                ContentType = typeof(EditViewModel),
            },
            new MenuItemViewModel
            {
                DisplayName = this.resource.GetString("Menu_Settings/Text"),
                IconText = ((char)0xE713).ToString(),
                Description = this.resource.GetString("Description_Settings/Text"),
                IsSelected = false,
                ContentType = typeof(SettingsViewModel),
            },
        };
        this.SelectedMenuIndex = 0;
    }
}

IResourceLoader のインスタンスを ServiceLocator 経由で取得し、下記のように GetString メソッドで引数のキー名に対応する多言語化リソースを取得するようにしました

DisplayName = this.resource.GetString("Menu_EditLogo/Text"),

Resources.resw 側で「.」の部分は「/」に置き換えて取得するのがポイントです

あとは通常通り XAMLバインディングするだけ

<prism:VisualStateAwarePage
    x:Class="XamarinLogoMaker.Views.TopPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamarinLogoMaker.Views"
    xmlns:vm="using:XamarinLogoMaker.ViewModels"
    xmlns:prism="using:Prism.Windows.Mvvm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{x:Bind ViewModel}"
    RequestedTheme="Dark">

    <Grid>
        <SplitView Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                   DisplayMode="CompactInline"
                   IsPaneOpen="{Binding Path=IsChecked, ElementName=humbergerButton, Mode=OneWay}">
            <SplitView.Pane>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="48"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    
                    <ToggleButton x:Name="humbergerButton"
                                  Width="48"
                                  Height="48">
                        <FontIcon Glyph="&#xE700;" />
                    </ToggleButton>
                    
                    <ListBox Grid.Row="1"
                             Padding="0"
                             Background="Transparent"
                             ItemsSource="{x:Bind ViewModel.MenuItems}"
                             SelectedIndex="{x:Bind ViewModel.SelectedMenuIndex, Mode=TwoWay}">
                        
                        <ListBox.ItemContainerStyle>
                            <Style TargetType="ListBoxItem">
                                <Setter Property="Padding" Value="0"/>
                                <Setter Property="Height" Value="48"/>
                                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                            </Style>
                        </ListBox.ItemContainerStyle>

                        <ListBox.ItemTemplate>
                            <DataTemplate x:DataType="vm:MenuItemViewModel">
                                <StackPanel Orientation="Horizontal"
                                            ToolTipService.ToolTip="{x:Bind Description}"
                                            Height="48">
                                    <FontIcon Glyph="{x:Bind IconText}"
                                              Width="48"/>
                                    <TextBlock Text="{x:Bind DisplayName}"
                                               Margin="15,0,0,0"
                                               VerticalAlignment="Center"
                                               FontSize="20"/>
                                </StackPanel>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </SplitView.Pane>

            <StackPanel Orientation="Vertical">
                <Border Background="#FF1F1F1F"
                        Height="48"/>
            </StackPanel>

        </SplitView>
    </Grid>
</prism:VisualStateAwarePage>

対象となっているのは下記の箇所です

                        <ListBox.ItemTemplate>
                            <DataTemplate x:DataType="vm:MenuItemViewModel">
                                <StackPanel Orientation="Horizontal"
                                            ToolTipService.ToolTip="{x:Bind Description}"
                                            Height="48">
                                    <FontIcon Glyph="{x:Bind IconText}"
                                              Width="48"/>
                                    <TextBlock Text="{x:Bind DisplayName}"
                                               Margin="15,0,0,0"
                                               VerticalAlignment="Center"
                                               FontSize="20"/>
                                </StackPanel>
                            </DataTemplate>
                        </ListBox.ItemTemplate>

左側のメニューラベルとツールチップを多言語化対応してみました

試しに実行

f:id:matatabi_ux:20150905210524p:plain

ちゃんと文字リソースが表示できましたね

ちなみに言語設定を下記のように English 優先に設定して

f:id:matatabi_ux:20150905211146p:plain

この状態でアプリを再起動すると

f:id:matatabi_ux:20150905211634p:plain

Strings/en-US/Resources.resw が参照されるようになり英語の表示に切り替わります

*1:x:Uid の詳細は次のページに説明があります(ただし英語かも;)

x:Uid ディレクティブ - Windows app development