i18n(国際化対応)を自前で対応させた話

i18n対応を自社で設定

オンラインでコンテンツを提供している弊社のシステムである、Alms (Aidemy Business) ではi18nというツールを用いて国際化対応を設定されており日本語と英語で現在受講可能です。Almsで表示されるテキストはエクササイズやテストなどのコンテンツ、UIの2種類で構成されておりユーザのアカウント設定画面より言語設定を変更すると任意の言語で表示されるようになっています。

以前の記事でも触れましたが自社でi18n対応できるようコンテンツ部分は変更されていましたが、UIもi18nを用いて国際化対応しましたので、今回の記事ではこちらについて説明いたします。

これによりi18nに関してより詳しい対応が可能となったので、ここからは以前利用していた外部ツールから自社によるi18n対応に置き換えることにより、可能になったことを技術的な観点からお話していきます。

弊社のAlmsのフロントエンドはReactを使っており、i18nのためにi18nextというライブラリを使用しました。

alms.dev


任意のUIテキストに言語の設定を追加しやすい

外部ツールではスクリプトを読み込むとclient上で表示されたテキストが適宜翻訳されるような仕組みが提供されていました。

日本語のUIを表示するのみだったシステムがスクリプト一行で多言語に翻訳されるため非常に使いやすいものでしたが、翻訳の変更に関して難しい点がありました。それは任意のUIテキストに対して変更を与えたい場合、用意されたUIからしか変更をかけられない点です。

導入当初は翻訳テキストの設定を簡単にできてよかったのですが、詳しい設定(日付設定など)をしたい場合なかなかUI上からではできませんでした。それが今回に関しては言語ごとにjsonファイルを用意しUIテキストに当てはめていくというシンプルな仕組みに変更されたため、カスタマイズ性が向上しました。

翻訳置換がスムーズに実行される

外部ツールではclient側でHTMLとしてrenderされたテキストから別の言語を置換するような仕組みでした。そのため、一瞬日本語が表示されてから英語に翻訳されるようなズレがページごとに発生してしまっていた。

今回の変更では、render前にテキストを置換しclientでテキストを表示するためにユーザは翻訳のズレを感じることがなくなりました。


i18nの運用体験が良くなった


外部ツールではページごとに置換用のテキストが設定されており、それを置換していくものでした。しかし、AlmsはSPAもしくは、URLがチームごとに存在する状況からうまく翻訳が置換されていないテキストが表示される場合がありました。特に開発環境ではうまく表示されないケースがあり、本番環境で確認せざるおえない状況が存在し作業コストが増える状況にありましたが、こちらも今回で特に環境ごとの差異は発生しないため解消されています。

 


i18n設定後に困った話


今回、i18nextのライブラリを使用し自社でi18nの対応を行ったのですが、少し困った部分が残ってしまいました。それは、コードの可読性と翻訳の追加作業の部分です。

 

コードの可読性

 

まず、コードの可読性についてですが、できあがったコードはi18nの実装自体は完了されたのでよいのですが今後運用するとなると可読性の問題がありそうです。

今回、コード上のUIテキストをi18n対応にしてくれる関数に置き換える作業をエンジニア以外の作業者、複数人に担当してもらったため複雑な作業は依頼できませんでした。そこでわかりやすく言語対応を示すkey名として[挿入するコンポーネントファイル名.(数字)]という命名方法で置換をお願いしていました。そのため、今後開発や修正をいれるときに実装されているコード上でkeyがどういうテキストを示すのか、テキストを別ファイルにまとめたのでひと目ではわからないようになりました(コンポーネントと翻訳ファイルのコードを参照する動作が増えた)。

これに関してはそれぞれの翻訳の箇所にfallback(デフォルトの指定されるテキスト)としてテキストを示す、もしくは日本語(英語)のkeyにしてしまうのがよいかなと思います。個人的には2つ目が開発者にとってやりやすそうだなと考えています。しかしどの言語に設定するべきかは少し相談が必要そうなのと、翻訳テキスト(日付、同じテキストに訳が複数ある、テキストが長い)によっては適宜対応が必要そうです。

fallbackを設定

 

key名を翻訳テキストにする



翻訳の追加作業

また、困った話の2点目の翻訳の追加作業に関してですが、UIテキストが増えたり言語設定が今後増えたときにそれぞれ言語の翻訳ファイルに該当のkeyが示すテキストが網羅して入っているかの確認作業が難しそうだなというお話です。

今回、それぞれの言語の翻訳ファイルにテキストを1000行あたり追加したのでそれぞれのテキストが言語ごとにはいっているかを確認するのは手動では困難です。これに関しては、すべてのkeyにテキストが入っているか、そもそもkeyがあるのかをチェックするスクリプトを作成し、CI上で毎回走らせればいいのではないかなと思っています。


 今後

元々利用していた外部ツールは、自社の運用コストを下げることができ国際化対応を手軽に実施できる、すごくよいものでした。お金をはらえばプロにも翻訳を頼めるサービスもやっており弊社のシステムとの相性があえばi18nをスケールさせやすいものであったと思います。

しかし、今回は運用コストやカスタマイズ性から自前でi18nを設定しました。これにより言語が増えても対応できるようになり、UIの追加、言語の追加を対応できるような基盤ができました。

今後としては実運用の際に少しつらそうなところが現時点ですでに見えているので、さらに運用の効率化をうまく設定していきたいと思います。

 

Aidemyアップデート情報 - 2022/08

2022年8月にリリースした機能から以下の2つを紹介します。

  1. 受講者のAidemy総利用時間を確認できるようになりました [Aidemy Business]
  2. デフォルトで入っているテストの出題範囲がわかるようになりました [Aidemy Business]

1.受講者のAidemy総利用時間を確認できるようになりました    [Aidemy Business]

管理画面[メンバーの進捗] からダウンロードできる進捗CSVに「総利用時間(時:分)」という項目が追加されました。受講者のマイページ確認や各コースへの遷移、回答などのアクションが行われている時間を記録しています。

ぜひ受講者の状況把握や学習フォローにお役立てください。

 

2.テスト設定時に出題範囲がわかるようになりました [Aidemy Business]

管理画面[テスト設定]で設定するテストを選択したとき、「テストの説明」という項目に、出題範囲についての説明を表示するようにしました。

 

今後もお客様の利便性を高めるべく開発を進めます。サービス改善の励みとなるので、引き続きAidemyへの要望・フィードバックをいただけますと幸いです。

 

ご要望・フィードバックはこちらから

 

Aidemyアップデート情報 - 2022/07

2022年7月にリリースした機能を紹介します。

  1. 個人のデジタルスキルを可視化するアセスメントテスト「DSAT (Digital Skill Assessment Test)」β版を公開
  2. 受講者画面ヘッダーから「おすすめカリキュラム」ページへアクセス可能に

1.個人のデジタルスキルを可視化するアセスメントテスト「DSAT (Digital Skill Assessment Test)」β版を公開


DX推進を実施するうえで必要なスキルを、「Standard」「Engineering」「DataScience」「BusinessPlanning」の4つに区分し、それぞれの区分に対してスキルレベルを数値化できるテストをリリースしました。(Aidemy Businessのみ) 

普段お使いのテスト機能から受験いただけます。

「DSAT β版」の詳細はこちら

 

2.受講者画面ヘッダーから「おすすめカリキュラム」ページへアクセス可能に

受講者画面のヘッダーに「おすすめカリキュラム」ページへのリンクを追加しました。

「おすすめカリキュラム」ページでは、身につけたいスキル・目指す職種に合ったおすすめのカリキュラム・コースを紹介しています。割り当てられたカリキュラムが終わった受講者が、次に受講するコースを選ぶ際に役立ちます。

「おすすめカリキュラム」ページはこちら

 

今後もお客様の利便性を高めるべく開発を進めます。サービス改善の励みとなるので、引き続きAidemyへの要望・フィードバックをいただけますと幸いです。

 

ご要望・フィードバックはこちらから

 

Aidemyアップデート情報 - 2022/06

Aidemy Update 2022年06月

管理画面のデザインが新しくなりました!

2022年6月の大きなリリースとして、Aidemy Business管理画面のリニューアルをいたしました。主なリニューアルのポイントは以下2点です。

  • 情報の一覧性と操作性の改善
  • 画面表示速度の改善

 

また、Aidemy開発チームとしては、管理画面の内部構造の整理をすることで、今後の開発スピードを高める土台作りを行うことができました。引き続き、改善や機能追加にご期待ください。

 

さて、本記事では新しい管理画面の改善点をご紹介します。

  1. 縦スクロール時に、どのデータがどの項目なのかわかりやすくなりました
  2. 横スクロール時に、どのユーザーを操作しているのかがわかりやすくなりました
  3. サイドメニュー開閉可能にし、一覧性が向上しました

 

1. 縦スクロール時に、どのデータがどの項目なのかわかりやすくなりました

縦スクロール時に項目名が固定表示されるように

これまでは、メンバー管理/進捗画面を大きくスクロールすると項目名(列名)が見えなくなり、今見えているデータがどの項目なのかわからなくなってしまうという課題がありました。

今回、スクロール時に項目名を固定表示にすることで、ユーザー情報の見やすさを向上しました。

 

2.横スクロール時に、どのユーザーを操作しているのかがわかりやすくなりました

どのユーザーのデータを見ているのかがわかりやすく

どのユーザーのデータを見ているのかがわかりやすく

ブラウザ幅が狭い状態でメンバー管理/進捗画面を使用すると、どのユーザーを操作しているのかわかりにくいという課題がありました。
これに対し、以下2点の改善を行うことでどのユーザーのデータを見ているのかがわかりやすくなりました。

  • 横スクロール時に「お名前」列を固定表示させる
  • 各列の偶数行に背景色(グレー)を設定

 

3.サイドメニュー開閉可能にし、一覧性が向上しました

クリック一つでサイドメニューを開閉できます

クリック一つでサイドメニューを開閉できます

クリック一つでサイドメニューを開閉できるようになりました。サイドメニュー幅を狭め、一覧部分を広げることで、ユーザー情報が見切れにくくなります。

 

今後もお客様の利便性を高めるべく開発を進めます。サービス改善の励みとなるので、引き続きAidemyへの要望・フィードバックをいただけますと幸いです。

 

ご要望・フィードバックはこちらから

 

戻り値の型が変化する関数を TypeScript で表現する方法

こんにちは。システム部の真部です。
普段は alms のフロントエンドとバックエンドの開発を担当しています。

この記事ではサービスを開発する中で遭遇した TypeScript にまつわるちょっとした問題とその解決策について紹介しようと思います。

使用する TypeScript のバージョンは 4.6.4 です。


戻り値の型が変化する関数

TypeScript の型システムには JavaScript の動的な側面を表現するために高度な機能が用意されており、 戻り値の型が引数の型に応じて変わるような関数の型も表現することができます。

簡単な例を使って考えてみようと思います。
以下の関数は数値または文字列値を受け取り、受け取った値を 2 倍にして返します。

function twice(x: any) {
    return x + x;
}

twice(2);
// => 4

twice('aa');
// 'aaaa'

このような関数の引数と戻り値の型はどのように表現するのが良いでしょうか。


ユニオン型

とりあえずユニオン型を使って素直に書いてみます。

function twice(x: number | string): number | string {
    return x + x;
    //    ~~~~~~~ Operator '+' cannot be applied to types 'string | number' and 'string | number'.(2365)
}

残念ながらこの方法は上手くいきません。
+ 演算子がユニオン型を許容しないためです。


オーバーロード

オーバーロードを使ってみるのはどうでしょうか。

function twice(x: number | string): number | string;
function twice(x: any) {
    return x + x;
}

const num = twice(2);
// const num: number | string

const str = twice('aa');
// const str: number | string

エラーが出なくなり、最初の例よりも正確に引数の型と戻り値の型を表現できるようになりました。

しかしながら、この記述だと戻り値の型について若干曖昧な点が残ってしまいます。 この関数は引数の値を 2 倍した値を返すので引数の型と戻り値の型は一致しているはずです。 number を渡せば number が返ってきますし、string を渡せば string が返ってくるはずですが、そのような引数と戻り値の関係が関数の型に反映されていません。

もう少し細かく定義してみるのはどうでしょうか。以下のような形です。

function twice(x: number): number;
function twice(x: string): string;
function twice(x: any) {
    return x + x;
}

const num = twice(2);
// const num: number

const str = twice('aa');
// const str: string

良さそうです。
ところで他の関数の中から呼び出すことを考えると number|string にも対応できた方が良さそうですがこの場合にも対応できるでしょうか。

function func(value: number | string): void {
    const result = twice(value);
    //                  ~~~~~~~
    // No overload matches this call.
    // Overload 1 of 2, '(x: number): number', gave the following error.
    //   Argument of type 'string | number' is not assignable to parameter of type 'number'.
    //     Type 'string' is not assignable to type 'number'.
    // Overload 2 of 2, '(x: string): string', gave the following error.
    //   Argument of type 'string | number' is not assignable to parameter of type 'string'.
    //     Type 'number' is not assignable to type 'string'.(2769)
}

twice の呼び出し時の引数の型は numberstring だけであり number|stringnumber,string のどちらとも互換性が無いためエラーになります。
number|string を関数の定義に追加しましょう。

function twice(x: number): number;
function twice(x: string): string;
function twice(x: number | string): number | string;
function twice(x: any) {
    return x + x;
}

function func(value: number | string): void {
    const result = twice(value);
    // const result: number | string
}

これでようやく引数の型のパターンを網羅できました。
ただ、あまり簡潔な表現とは言えないかもしれません。 引数の型が増えた場合すべてのパターンを網羅するのはかなり大変そうです。number,string の他にもう一つ型が増えた場合、引数の型のパターンは 7 通りに増えます。(2 の 3 乗 -1 通り)


ジェネリクス

コード量を抑える方法を検討してみましょう。
ジェネリクスはどうでしょうか。
この方法なら numberstringnumber|string も受け取れるようになります。

function twice<T extends number | string>(x: T): T;
function twice(x: any) {
    return x + x;
}

良さそうに見えますがこの方法にも欠点があります。
リテラルを渡すと型推論の結果が厳密になり過ぎてしまう点です。

const num = twice(2);
// const num: 2
// num の値は実際には4なので不正確どころか間違ったものになってしまっている

const str = twice('aa');
// const str: "aa"
// str の値は実際には'aaaa'なので不正確どころか間違ったものになってしまっている

一応、型パラメータを明示することでこの問題を回避することはできますが、これでは利便性や保守性が損なわれてしまいます。

const num = twice<number>(2);
// const num: number

const str = twice<string>('aa');
// const str: string

Conditional Types

オーバーロードには記述量の問題があり、ジェネリクスにはリテラルを渡したときの型推論の結果が厳密になり過ぎる問題があることが分かりました。

これらを同時に解決するには引数の型を基に戻り値の型を決定するロジックを簡潔に記述する方法が必要になるわけですが、ちょうど TypeScript にはそのような仕組みが用意されています。

それが Conditional Types であり、これを使うと型パラメータの値に基づいて型を決定するロジックを記述することができます。

また、Conditional Types は 型パラメータがユニオン型のときに Distributive Conditional Types と呼ばれる仕組みによってユニオン型を構成する型ごとに型を決定するロジックを適用します。これによって型パラメータの値がユニオン型の場合にも対応することができます。

言葉よりもコードで表現した方が分かりやすいので例を見てみましょう。

function twice<T extends number | string>(x: T): T extends number ? number : string; // <- T の値に基づいて number と string のどちらなのか決定する
function twice(x: any) {
    return x + x;
}

function func(value: number | string): void {
    const result = twice(value);
    // const result: number | string

    // Distributive Conditional Types によって
    // number|string extends number ? number:string は
    // (number extends number ? number:string) | (string extends number ? number:string) となり、
    // 上記を計算すると number|string となります
}

const num = twice(2);
// T の型は数値リテラル(2) であり、2 extends number は true なので
// T extends number ? number:string を評価した結果は number になります
// 従って num の型は number になります

const str = twice('aa');
// T の型は文字列リテラル('aa') であり、'aa' extends number は false なので
// T extends number ? number:string を評価した結果は string になります
// 従って str の型は string になります

これでコード量を抑えつつ、戻り値の型が変化する関数の型を記述することができました。


最後に

Conditional Types を使うことによって関数の型を簡潔に、正確に表現できることが分かりました。 TypeScript には JavaScript の振る舞いに追従するために非常に柔軟で表現力豊かな型システムが用意されています。

TypeScript の機能を使いこなすには型に関する計算に慣れる必要がありますが、そうすることでコードの意図が明確になり可読性が向上します。 また、エディタや lint からのフィードバックが正確になり、開発者体験がより良いものになります。

最後までご覧頂きありがとうございました。
アイデミーでは TypeScript を活用してシステムの改善を進めています。

Aidemyアップデート情報 - 隔週報 2022-W16/17

2022年 第16 - 17週、4月18日 (月) - 4月28日 (木) にリリースした機能を紹介します。

  1. 初回ログイン時に受信メール許諾モーダル追加
  2. Aidemy Premiumユーザーに対する招待メールの文言修正

 

1. 初回ログイン時に受信メールの許諾モーダルを追加

初回ログイン時に、Aidemyからユーザーに配信しているお知らせメールの受信設定を行えるようになりました。
お知らせメールでは以下の情報をお伝えしています。

  • コース・機能のアップデート
  • 受講ライセンス期限
  • キャンペーン・セミナーの告知
  • 活用事例などのお役立ち情報

メールでの通知が不要な場合は、受信を拒否することもできます。

しかし、Aidemyを最大限に活用いただくため、ぜひお知らせメールを受信いただくことをおすすめしています。

 

2. Aidemy Premium ユーザーに対する招待メールの文言修正

これまで、個人向けAIプログラミングサービス「Aidemy Premium」のユーザーに対する招待メールに、ユーザーが受講できないコースについての記載があり、混乱を招いていました。今回、受講可能なコースのみを案内するようメール文言を修正しました。

ユーザーは、自身が学習できるコースを正しく把握することができるようになりました。

Aidemy Premiumの詳細はこちら

 

 

今後も新機能リリースや不具合修正など、お客様の利便性を高めるべく開発を進めてまいります。サービス改善の励みとなりますので、引き続きAidemyへの要望・忌憚なきフィードバックをいただけますと幸いです。

ご要望・フィードバックはこちらから

 

所属・タグ・ライセンス有効期間の設定上限を500に拡大。柔軟な受講者管理が可能に - 隔週報 2022-W14/15

f:id:aidemy-blog:20220418122038p:plain

2022年 第14 - 15週、4月4日 (月) - 4月15日 (金) にリリースした機能を紹介します。

  1. 所属・タグ・ライセンス有効期間の設定上限を500に拡大。柔軟なメンバー管理が可能に

 

---

1. 所属・タグ・ライセンス有効期間の設定上限を500に拡大。柔軟なメンバー管理が可能に

f:id:aidemy-blog:20220418122232p:plain

前回の隔週報で紹介した「カリキュラムの設定上限を200に拡大」に続いて、メンバー管理の柔軟性を高めるリリースです。

 

今回は、Aidemy利用ユーザの増加に伴い、所属・タグ・ライセンス有効期間の設定上限を100から500に拡大しました。所属部署・チーム数が多く、細かにメンバーを分類して管理をしたい場合は、ぜひご活用ください。

 

また、このリリースに合わせて「操作マニュアル」の内容も更新しています。

管理画面の「操作マニュアルダウンロード」ボタンをクリックする、もしくはこちらから確認いただけます。

f:id:aidemy-blog:20220418124116p:plain



今回のリリースも、多くのユーザー様からの要望をいただいたおかげで生まれました。

サービス改善の励みとなりますので、今後も引き続きAidemyへの要望・忌憚なきフィードバックをいただけますと幸いです。

 

ご要望・フィードバックはこちらから