Angularでもいい感じに多言語化したい

はじめに

4月からVBおじさん改めナウでヤングなWebエンジニアになりました弊社です。

MS信者の弊社としましては、Blazorをぶっこんでヒャッハーしたいのですが、GAしていないものをプロダクションに投入するのはまぁ・・・ねぇ・・・*1

github.com

じゃけんAngularを投入しましょうね~ということになったのですが、Angularで多言語化するにはどうすればいいのかよく分からなかったので確認してみました。

ドキュメント

困ったら公式ドキュメントを参照する。万葉集にも書かれています。

公式ドキュメント答えて曰く

  1. デフォルトだとen-USしかロケールがインポートされていないから、他のロケールCurrencyPipeとかを使いたかったら自分でいい感じにインポートしてね(んで、ng serveとかng buildでパラメータとして与えてねとのこと)
  2. 翻訳したいタグに片っ端からi18n属性を放り込んでいってね
  3. ng xi18nでそのロケール用の翻訳用のファイルを生成して、そこに翻訳した結果を入力してね
  4. 翻訳用のファイルをアプリケーションに統合するにはAOT(Ahead of time)もしくはJIT(Just in time)の二つの手段があって、AOTは事前に各ロケール別のパッケージを生成しておく必要があるけどはやいよ。JITは実行時にやるから事前の準備は要らないけどおそいよ。好きなほうを選んでいってね

だそうです。

今回はAOTでやってみることにします。

angular.io

構成

f:id:jyuch:20190504224201p:plain

今回は、ビルド済みのAngularアプリケーションをnginxでホストします。また、APIサーバとして.NET Core Web APIを使用します。APIサーバはnginxでリバースプロキシをかますことでいい感じにAngularアプリケーションからアクセスできるようにします。

f:id:jyuch:20190505005422p:plain

クライアント側はhogefugaの二つのコンポーネントがあり、下部のリンクをクリックすると交互に画面が遷移します。

カウントはそれぞれの画面が遷移した回数を表し、これはクライアント個々の回数ではなくアプリケーション全体の数をカウントしています。そのためカウントはサーバ側のAPIでカウントをしています。この部分は.NET Core Web APIで実装されています。

言語化

まず、ベースとなるアプリケーションを用意します。

今回はあらかじめこちらに用意しておきました。(キュー〇三分クッキング風に)

github.com

翻訳属性の付与

とりあえずは多言語化する箇所にひたすらi18n属性を追加していきます。

github.com

翻訳ファイルの生成

以下のコマンドを使用し翻訳ファイルを生成します。

ng xi18n --output-path src/locale

デフォルトではsrc直下に作成されるのですが、それだとなんか嫌なのでとりあえずlocale以下に格納するようにします。

翻訳ファイルへの翻訳の記入

さっき作った翻訳ファイルをコピーし、翻訳(今回は日本語)を入力します。

github.com

serve及びbuild時の変換設定

デバッグ時及びビルド時に翻訳結果をマージできるように設定ファイルをいい感じに書き換えます。

github.com

すると、コマンドで日本語の翻訳結果がマージされたアプリケーションが起動します。

npm run startjp

f:id:jyuch:20190505020109p:plain

デプロイ

URL配置

今回は以下の感じでURLに配置します

  • /app/ja:日本語
  • /app/en:英語
  • /api:Web API

Angularはデフォルトだと/直下に配置される前提でビルドされるっぽいので、それぞれのURLから展開できるようにbase-hrefを設定します。

github.com

ビルド

それぞれ

dotnet publish -c Release -r win10-x64 --self-contained true

npm run build; npm run buildja

で成果物をビルドします。

配置

成果物をサーバにコピーして以下の感じに展開します。

└─/path/to/nginx/www/root
   └─app
       ├─en
       │  └─index.htmlとか
       └─ja
          └─index.htmlとか

そうしたら、いい感じにnginxの設定ファイルを書きます。

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  s00010;
        
        location /app/ja/ {
            root   html;
            index index.html index.htm;
            try_files $uri /app/ja/index.html;
        }
        
        location /app/en/ {
            root   html;
            index index.html index.htm;
            try_files $uri /app/en/index.html;
        }
        
        location /api/ {
            proxy_pass    http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection keep-alive;
            proxy_set_header   Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

確認

おわりに

悩んだ時間の大半はnginxの設定でした。つらいね

github.com

おわり

*1:4/18に正式プレビューになったのでそろそろGAだとは思うのですが

VB.NETでReDim Preserveを使うくらいなら配列を使うのをやめたら?

はじめに

弊社のブログで現在一番アクセス数が多いのは(個人的には意外ですが)ReDimの記事です。

jyuch.hatenablog.com

この記事でもReDim Preserveには否定的な感じで書いていますが、現在でもこの意見は変わりません。

今北産業

  • 添え字操作がバグの温床になる
  • 配列の使用意図を読み取るのに文脈に読み解かないといけないから可読性がマッハで低下する
  • マジで気を付けないとパフォーマンス的にデメリットになるし、気を付けられる人はそもそもVBつkにゃーん

⇒定数として複数の値を定義したり、配列しか受け入れないメソッドへの引数で使う以外でVBで配列とReDim Preserveを使う価値はない

How about ReDim?

ReDimってどういう仕組みで配列の拡張または縮小を行っているのか確認してみましょう。

Dim a As String() = New String() {"Hello", "World"}
ReDim Preserve a(2)

のコードは以下のコードと等価です。

Dim a As String() = New String() {"Hello", "World"}
Dim b(2) As String

For i As Integer = 0 To a.GetLength(0)
    b(i) = a(i)
Next

a = b

単純に言ってしまうと、新しいサイズの配列を生成して値をコピーしているだけです。

ReDim Preserveに否定的な理由

そんじゃ、なんで弊社がReDim Preserve(というか配列そのもの)に否定的な意見*1なのかと言いますと

配列の添え字の制御が面倒(かつバグの温床になりやすい)

ReDim Preserveを使う場面って最初からデータの数が正確に予想できない場合がほとんどだと思います。

例えば以下のコードのように入力をバッファする場合、正確な数が予想できないため必要に応じて配列を拡張する必要があります。

Dim currentIndex = 0
Dim buffer(4) As String

While True
    Dim input = Console.ReadLine()

    If input = "q" Then
        Exit While
    End If

    If currentIndex > buffer.GetLength(0) Then
        ReDim Preserve buffer(buffer.GetLength(0) + 5)
    End If

    buffer(currentIndex) = input
    currentIndex += 1
End While

For Each it In buffer
    If it <> Nothing Then
        Console.WriteLine(it)
    End If
Next

入力された値をバッファし、最後に一気に出力しています。

それで、こちらがQueue(Of T)を使ったバージョン。

Dim buffer As New Queue(Of String)

While True
    Dim input = Console.ReadLine()

    If input = "q" Then
        Exit While
    End If

    buffer.Enqueue(input)
End While

For Each it In buffer
    Console.WriteLine(it)
Next

余計な変数が1つ消え、めんどくさい添え字計算も省略できています。*2

VBの主戦場たる生産性アプリケーションでは基本的にそこまでクリティカルな性能が求められることはなく*3、むしろコードでどこまでドメインを表現できるか・保守性の高いコードであるかが求められると考えています。

後述しますが、そのコードでのドメインの表現・保守性を考えると、配列の添え字を操作するよりも適切なコレクションクラスを用いてドメインのセマンティクスをコード上で直接表現したほうが保守性が高くなるのではないでしょうか。

使用意図が分かりずらい

添え字計算を駆使すれば、配列はキュー*4・スタック・リングバッファー等々のセマンティクスを表現できます。

表現できるのはいいのですが、その駆使された添え字計算について後から読まなくてはならないあなたの後任の事も考えてあげましょう。

つまるところ、ぐちゃぐちゃな添え字計算を読み取って使用意図をくみ取るのと、専用のコレクションが使われているのを読み取るのでは、将来あなたの後任にとってどちらが楽ですか?という事です。

多用するとパフォーマンス的に不利になりやすい

上でも述べましたが、ReDim Preserveは新しい領域を確保して値をコピーした後に古い領域をガベージコレクタにブン投げます。

特に配列のサイズが大きい*5ケースでReDim Preserveを多用するとメモリアロケーションガベージコレクションが多発してパフォーマンスに明らかな悪影響を及ぼします。

代替手段

ほとんどのケースでは

docs.microsoft.com

の中から適したコレクションクラスを選択するだけで要求を満足するはずです。

(古い記事では特に)配列の代替としてArrayListを推しているケースが多いですが、パフォーマンス・型安全の点からみてもジェネリックコレクションと比べたメリットが無いので2秒でゴミ箱にぶち込みましょう。

おわりに

  1. ジェネリックコレクションを知らないのなら、まずはそれを勉強しろ
  2. パフォーマンスを重要視したいのはわかるが、お前のアプリケーションでそこまでの最適化が本当に必要か胸に手を当てて考えてみろ
  3. そうでないならVB.NETでReDim Preserveを使うくらいなら配列を使うのをやめたら?

*1:ただし生産性アプリケーションの開発に限る

*2:ちなみに、配列版は実はバグがあります。探してみましょう!!

*3:もちろん速いに越したことはありませんが、求められる速度はプリミティブなメモリ操作が必要になるほどではないと考えています

*4:上のコードの様に

*5:具体的にいうと配列のサイズが86キロバイトを超えるケース。64ビットで動作していると仮定すると、文字列の配列で要素数が11008を超えるような

Windowsの記憶域に他から引っ張ってきたSSDを追加したい

はじめに

弊社ではVMware Workstationで検証環境を構築しており、その仮想マシンの格納用にSSDをシンプル構成(いわゆるRAID0)の記憶域(Windowsでのストレージプール)を構成しています。

記憶域用に新品を買ってくるのではなく、他で余ったSSDが出たら都度交換・追加をしているのですが単純にケーブルを繋いでも記憶域に追加できないのでそれについてです。

たまにしかやらないので都度ググっていたのが、面倒になったので自分用にメモします。

既存パーテーションの削除

まずは既存パーテーションを削除します。普通のパーテーションならディスクの管理から一撃なのですが、EFIパーテーションはディスクの管理から消せないのでdiskpartで消し去る必要があります。*1

PS F:\> diskpart.exe

Microsoft DiskPart バージョン 10.0.17763.1

Copyright (C) Microsoft Corporation.
コンピューター: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓

DISKPART> list disk

  ディスク      状態           サイズ   空き   ダイナ GPT
  ###                                          ミック
  ------------  -------------  -------  -------  ---  ---
  ディスク 0    オンライン           476 GB      0 B        *
  ディスク 1    オンライン          1863 GB      0 B        *
  ディスク 4    オンライン          1024 GB      0 B        *
  ディスク 5    オンライン           447 GB   446 GB        *

DISKPART> select disk 5

ディスク 5 が選択されました。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    システム               200 MB    20 KB

DISKPART> select partition 1

パーティション 1 が選択されました。

DISKPART> delete partition override

DiskPart は選択されたパーティションを正常に削除しました。

DISKPART> exit

DiskPart を終了しています...

nasunoblog.blogspot.com

Can PoolフラグをTrueにする

多分誤ってプールに追加しないように一度使用したディスクはCan PoolフラグにFalseが設定されます。

そのため、Can Poolフラグをリセットする必要があります。

PS F:\> Get-PhysicalDisk

DeviceId FriendlyName         SerialNumber                             MediaType CanPool OperationalStatus HealthStatus
-------- ------------         ------------                             --------- ------- ----------------- ------------
2        SanDisk ▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓                             SSD       False   OK                Healthy
3        INTEL ▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                       SSD       False   OK                Healthy
5        SanDisk ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                             SSD       False   OK                Healthy
1        TOSHIBA ▓▓▓▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓▓▓                                HDD       False   OK                Healthy
0        INTEL ▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ SSD       False   OK                Healthy

PS F:\> Get-PhysicalDisk -SerialNumber "▓▓▓▓▓▓▓▓▓▓▓▓"

DeviceId FriendlyName         SerialNumber MediaType CanPool OperationalStatus HealthStatus Usage            Size
-------- ------------         ------------ --------- ------- ----------------- ------------ -----            ----
5        SanDisk ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ SSD       False   OK                Healthy      Auto-Select 447.13 GB

PS F:\> Get-PhysicalDisk -SerialNumber "▓▓▓▓▓▓▓▓▓▓▓▓" | Reset-PhysicalDisk

PS F:\> Get-PhysicalDisk

DeviceId FriendlyName         SerialNumber                             MediaType CanPool OperationalStatus HealthStatus
-------- ------------         ------------                             --------- ------- ----------------- ------------
2        SanDisk ▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓                             SSD       False   OK                Healthy
3        INTEL ▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                       SSD       False   OK                Healthy
5        SanDisk ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                             SSD       True    OK                Healthy
1        TOSHIBA ▓▓▓▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓▓▓                                HDD       False   OK                Healthy
0        INTEL ▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ SSD       False   OK                Healthy

signal-flag-z.blogspot.com

あとはコントロールパネルからディスクを記憶域に追加すればOKです。

おわり

*1:消す必要があるのかはしらん