2020年6月11日木曜日

Oracle APEXアプリケーションのグローバル化(5) - 言語の切り替え

Oracle APEXアプリケーションをグローバル化する手順の説明です。こちらの記事からの続きになります。

言語の切り替え


多言語に対応したアプリケーションの言語を切り替える方法は、グローバリゼーション属性のアプリケーション言語の導出元として設定します。今まではセッションとしていました。


アプリケーション言語の導出元として設定できる選択肢は6つあります。
  1. NLSなし(アプリケーションは翻訳されない)
  2. アプリケーションのプライマリ言語
  3. ブラウザ(ブラウザの言語プリファレンスを使用)
  4. アプリケーション・プリファレンス(FSP_LANGUAGE_PREFERENCEを使用)
  5. アイテム・プリファレンスを使用(プリファレンスを含むアイテムを使用)
  6. セッション
これらのそれぞれについて説明していきます。

NLSなし(アプリケーションは翻訳されない)


アプリケーション言語の導出元のデフォルトはアプリケーションのプライマリ言語となっています。アプリケーションの翻訳をしていない場合は、デフォルトの設定とNLSなしの設定で、ほとんど動作に違いはありません。

Oracle APEXのアプリケーションを翻訳する際に、ベース・アプリケーションのアプリケーションのプライマリ言語の設定にかかわらず、同じ言語の翻訳を登録することができます。

例えば、今まで作成してきたアプリケーション、竹家のメニューに言語のマッピング日本語(ja)を追加します。


アプリケーションのプライマリ言語日本語(ja)ですが、jaの翻訳も登録されています。


レポートのタイトルを日本語メニューから、言語依存メニューに変更します。


シードを実行したのち、翻訳リポジトリにアクセスして、言語依存メニューというリージョン名をそれぞれの言語で翻訳します。

 日本語(ja)  英語(en)  簡体中国語(zh-cn)  韓国語(ko) 
 日本語メニュー  English Menu  中文菜单 한국어 메뉴

翻訳リポジトリからみると、以下のようになります。


翻訳ができたので、すべての翻訳済みアプリケーションをパブリッシュします。

アプリケーションがこのように翻訳済みになっている状態で、アプリケーション言語の導出元NLSなしの場合、翻訳形式として何が登録されていても、ベース・アプリケーションのラベルが表示されます。NLSなしの場合、このレポートの表示は以下になります。


翻訳済みのアプリケーションが採用されないので、タイトルや列名はベース・アプリケーションでの設定内容が表示されます。

アプリケーション側で多言語対応するために使用した機能は以下の影響を受けます。
SYS_CONTEXT('USERENV','LANG')
は常にUSを返します。これは、データベースのセッション取得時にNLS_LANGUAGEとしてAMERICAN、NLS_TERRITORYとしてAMERICAが設定された結果です。

APEX_LANG.LANGおよびMESSAGEファンクションは、アプリケーションのプライマリ言語の設定を参照しています。

これらは実機で動作を確認した結果です。条件によっては異なる結果になる可能性もあります。基本的にNLSなしの設定をする場合は、アプリケーションでこれらのファンクションは必要ありません。

NLSなしの設定は、アプリケーションがすでに翻訳されていて、その上でアプリケーションのデバッグする際に翻訳なしのベース・アプリケーションを確認する場合に使用します。本番運用するアプリケーションへの設定はお勧めしません。

ベース・アプリケーションのプライマリ言語と同じ翻訳が登録されていると、翻訳されたアプリケーションが優先されます。アプリケーションの変更はベース・アプリケーションに対して行われます。翻訳されたアプリケーションが実行されていると、変更したアプリケーションの動作確認をするには、かならずシードとパブリッシュが必要になります。アプリケーション言語の導出元をNLSなしに設定するとベース・アプリケーションが優先されますが、生産性を落とさないためには、ベース・アプリケーションに設定されている言語と同じ翻訳は登録しない方がよいでしょう。

アプリケーションのプライマリ言語


最も一般的な設定です。アプリケーションの翻訳がされている場合は、その言語で画面の表示がされます。アプリケーションのプライマリ言語として設定した言語に対応した翻訳がない場合は、ベース・アプリケーションのラベルがそのまま使用されます。


アプリケーションのプライマリ言語の設定に対応したNLS_LANGUAGE、NLS_TERRITORYがセッションに設定されるため、SYS_CONTEXTファンクションも対応した短縮形を返します。

とはいえ、アプリケーションのプライマリ言語を指定する形で言語設定を固定しますので、この設定を行うアプリケーションは、翻訳する意味がありません。この設定は翻訳をする予定のないアプリケーションに使います。翻訳する予定のないアプリケーションには、NLSなしより、こちらの設定が推奨されます。

ブラウザ(ブラウザの言語プリファレンスを使用)


ブラウザが送信してくるAccept-Languageヘッダーの先頭に設定されている言語によって、アプリケーションの言語を決定します。Chromeを例にとると、以下のような画面では日本語が選択されます。


上記の設定で、Chromeは以下のAccept-Languageヘッダーを送信していました。
Accept-Language = ja,en-US;q=0.9,en;q=0.8
設定されている言語に対応した翻訳が存在する場合は良いのですが、存在しない場合に注意が必要です。例えば以下のように台湾などで使用される繁体字の中国語が優先されているとします。


この設定で、演習で作成したアプリケーションにアクセスすると表示が以下になります。


対応した翻訳が存在しないため、ベース・アプリケーションに設定されている文字列が使用されます。翻訳はありませんが、NLS_LANGUAGE、NLS_TERRITORYはブラウザの設定が反映されるので、SYS_CONTEXT('USERENV','LANG')の返り値は'ZHT'になります。表TKY_MENUS_TLにLANGUAGE列がZHTのデータはないので、一行も表示されません。

また、ブラウザの言語が英語(イギリス)が先頭の場合は以下になります。
Accept-Language = en-GB,en;q=0.9,ja;q=0.8,en-US;q=0.7
en-GBで、これと一致する翻訳アプリケーションがない場合、拡張部分のGBを除いたenで翻訳を探します。これに一致すると、その翻訳が使用されます。画面上の表示は英語の翻訳済み画面になりますが、NLS_LANGUAGEおよびNLS_TERRITORYはそれぞれ”ENGLISH"、"UNITED KINGDOM"が設定され、SYS_CONTEXT('USERENV','LANG')の結果としてGBが返されます。

このような動作になるため、アプリケーション言語をブラウザにする場合は、翻訳がない言語でのアクセスを想定してベース・アプリケーションは英語で作成することが推奨されます。また、SYS_CONTEXTファンクションを使用する際には、翻訳がない場合の対応も考えてコーディングする必要があります。例えばビューTKY_MENUS_VLの定義は以下のようになります。
CREATE OR REPLACE FORCE VIEW "TKY_MENUS_VL" ("ID", "MENU_NAME", "VOLUME", "PRICE") AS 
  select o.id, l.local_menu_name as menu_name, apex_lang.lang(o.volume) as volume, o.price
from tky_menus o join tky_menus_tl l on o.id = l.menu_id
where l.language = 
CASE WHEN SYS_CONTEXT('USERENV','LANG') IN ('US','JA','ZHS','KO') THEN
  SYS_CONTEXT('USERENV','LANG')
ELSE
  'US'
END
/
設定が簡単そうに見えますが、ブラウザの設定を使う場合はこういった対応が要求されます。特にSYS_CONTEXTファンクションを使用する場合は要注意です。

アプリケーション自体に言語を認識させて処理をする仕組みを実装している場合は、ブラウザの設定による任意の言語を受け付けるよりは、アプリケーションが対応している言語を利用者に選択させる方が合理的でしょう。Oracle APEXが提供している仕組み(アプリケーションの翻訳やAPEX_LANGパッケージ)のみを使っている場合は、ブラウザの設定はうまく動くはずです。

アプリケーション・プリファレンス(FSP_LANGUAGE_PREFERENCEを使用)


ユーザーごとに言語設定を持たせます。以下のようにAPEX_UTIL.SET_PREFERENCEプロシージャをFSP_LANGUAGE_PREFERENCEと言語を引数として呼び出します。
begin
   apex_util.set_preference
   (
     p_preference => 'FSP_LANGUAGE_PREFERENCE',
     p_value => :P2_LANGUAGE,
     p_user => :APP_USER
   );
   commit;
end;
p_userにユーザー名を設定しているので、これはログイン・ユーザーにたいして設定が保存され、次回以降のログインでも言語設定が有効になります。

このプリファレンスを使う実装例を紹介します。

以下のような非モーダルのダイアログとしてページを作成し、そこに言語設定を行うページ・アイテムを設定します。


ページ・アイテムは選択リストとします。翻訳済みの言語を選択できるようにします。


ページ・アイテムの変更時にPL/SQLコードを実行する動的アクションを定義します。


言語プリファレンスを設定するページを作成する必要はありますが、アプリケーションが対応している言語の利用に限定することができます。

指定できる言語コードについては、Oracle Application Expressのグローバリゼーション・コードを参照してください。


アイテム・プリファレンスを使用(プリファレンスを含むアイテムを使用)


いままで紹介してきた方法は、Oracle APEXが言語を設定する仕組みを提供しています。アイテム・プリファレンスを使用する方法は、言語の選択をコーディングで解決します。例えば、先ほどのプリファレンスの設定を優先するが、その設定が無い場合はブラウザの設定を参照する、といった実装が可能になります。アイテム・プリファレンスを使用する場合はコーディングは必須ですが、この次に説明するセッションでは、Oracle APEXが提供する仕組みと、コーディングによる言語の選択の両方を行うことができます。新たに開発する場合はこちらの設定を利用するよりはセッションの方がお勧めです。

共有コンポーネントアプリケーション・アイテムとしてFSP_LANGUAGE_PREFERENCEを登録します。


あとは、PL/SQLコードを書ける場所であれば、プロセスでも動的アクションでもアプリケーション・アイテムFSP_LANGUAGE_PREFERENCEに言語を設定すると、それがアプリケーションに反映されます。とはいえ、少なくても初期値は共有コンポーネントアプリケーションの計算によって設定することになるでしょう。


先ほどの例であげた、プリファレンスの設定を優先して、それがない場合はブラウザの設定を使うというPL/SQLコードの例が以下になります。
declare
   l_lang varchar2(80);
   l_accept_langs varchar2(400);
   l_lang_arr apex_t_varchar2;
begin
   l_lang := apex_util.get_preference('FSP_LANGUAGE_PREFERENCE');
   if l_lang is null then
       l_accept_langs := owa_util.get_cgi_env('HTTP_ACCEPT_LANGUAGE');
       if l_accept_langs is not null then
           l_lang_arr := apex_string.split(l_accept_langs, ',');
           if l_lang_arr.count > 0 then
               l_lang := l_lang_arr(1);
               if instr(l_lang, ';') > 0 then
                  l_lang := substr(l_lang,1,instr(l_lang, ';')-1);
               end if;
           end if;
       end if;
   end if;
   if l_lang is null then
     l_lang := 'en';
   end if;
   apex_debug.info('Language is set to ' || l_lang);
   return l_lang;
end;
プリファレンスはユーザーに紐づいて設定されますので、計算ポイントをユーザー認証後に設定し、計算タイプはPL/SQLファンクション本体、計算のコードとして上記のコードを設定します。


このようなコーディングを行うことで、言語を色々な方法で選択することが可能になります。

また、言語を選択するドロップダウン・メニューも、この設定を活用して実装できます。


アプリケーション・アイテムFSP_LANGUAGE_PREFERENCEをブラウザから設定変更できるように設定をします。セキュリティのセッション・ステート保護は、デフォルトでは制限付き - ブラウザから設定不可なので、チェックサムが必要 - セッション・レベルに変更します。


共有コンポーネントナビゲーション・バー・リストを開き、デスクトップ・ナビゲーション・バーの編集を行います。


デスクトップ・ナビゲーション・バーに5つのエントリを追加します。言語(&FSP_LANGUAGE_PREFERENE.)のエントリが他の4つのエントリの親になります。また、このエントリにはターゲットの設定がありません。


言語設定を行う他の4つのエントリは以下の設定を行います。


言語設定はページ処理の一番最初に行われます。リスト・エントリに指定されているFSP_LANGUAGE_PREFERENCEの設定はその後に行われます。ですので、ターゲット・ページを&APP_PAGE_ID.としてFSP_LANGUAGE_PREFERENCEを設定しても、変更が画面に反映されるのは、再度ページをリロードした後になります。この現象を回避するために、空白のページredirectを作成し、そこから呼び出し元のページにリダイレクトさせることで、言語の設定が行われた後にページがロードされるようにします。

ページの別名redirectとして作成し、ヘッダーの前ブランチを登録します。ブランチでは無条件でページ・アイテムP5_TARGET_PAGEに渡されたページ、つまりドロップダウン・リストを使って言語を切り替えた時に表示されていたページに再度リダイレクトします。ページ・アイテムP5_TARGET_PAGEを登録するため、静的コンテンツのリージョンをContent Bodyに登録します。ただし、ページのレンダリングが行われる前に登録したブランチによってリダイレクトしますので、このページが画面に表示されることはありません。


アイテム・プリファレンスを使用はこの例のように使用します。

セッション


今回の設定で主に使ってきた設定です。サインインの画面に表示される言語セレクタから言語を選択します。


言語セレクタから言語を選択したのち、URLを確認するとパラメータにp_lang=言語が追加されていることを確認できると思います。アプリケーション言語の導出元として使用されるのは、このp_langパラメータです。そのため、言語セレクタを使用せずに直接URLにp_lang=言語を追加して、アプリケーションの言語を切り替えることも可能です。

外部サイトに埋め込んだアプリケーションの直リンクに言語設定も含める場合などは、このセッションの設定を使うことができます。また、言語セレクタは自動的に表示されるようになっているので、追加のコーディグが不要です。作成したアプリケーションがOracle APEXが提供しているログイン・ページをそのまま使用しているのであれば、このセッションの設定で事足りるケースが多いでしょう。


海外のブログに言語セレクタの見かけを変更する方法が紹介されていました。


ページにJavaScriptとCSSの記述を追加します。

JavaScriptはページ・ロード時に実行するコードとして以下を指定します。
$(".a-LinksList-item").addClass("t-Button").addClass("t-Button--pill");
CSSはインラインで以下を指定します。
.a-LinksList--lang {text-align: center;}
a.a-LinksList-link {position: relative; z-index: 2;}
以上の設定で、切り替えがリンクではなくボタンに変わります。

言語の設定、または切り替えはAPEX_UTIL.SET_SESSION_LANGプロシージャにて実施することも可能です。このプロシージャを使い、アイテム・プリファレンスを使用のセクションで行ったログイン時に言語を設定するコーディングと、ナビゲーション・バーでの言語切り替えを実装してみます。

セッションの場合はアプリケーション・プロセスによって、ユーザー認証後の言語設定を行います。

PL/SQLコードとしては以下になります。
declare
   l_lang varchar2(80);
   l_accept_langs varchar2(400);
   l_lang_arr apex_t_varchar2;
begin
   l_lang := apex_util.get_preference('FSP_LANGUAGE_PREFERENCE');
   if l_lang is null then
       l_accept_langs := owa_util.get_cgi_env('HTTP_ACCEPT_LANGUAGE');
       if l_accept_langs is not null then
           l_lang_arr := apex_string.split(l_accept_langs, ',');
           if l_lang_arr.count > 0 then
               l_lang := l_lang_arr(1);
               if instr(l_lang, ';') > 0 then
                  l_lang := substr(l_lang,1,instr(l_lang, ';')-1);
               end if;
           end if;
       end if;
   end if;
   if l_lang is null then
     l_lang := 'en';
   end if;
   apex_debug.info('Language is set to ' || l_lang);
   apex_util.set_session_lang(l_lang);
end;
違いは一行だけで、設定する言語をreturnで戻す代わりに、APEX_UTIL.SET_SESSION_LANGプロシージャを呼び出して、言語を設定しています。


メニュー自体の設定では、パラメータp_langを設定し、表示中のページを再表示します。p_langはシステムによって認識されるため、アイテム・プリファレンスのように追加のリダイレクトを行う必要はありません。


アイテム・プリファレンスを使用する場合と比較すると、API呼び出しによる言語設定や、p_langパラメータがシステムによって解釈される、といったことより、コーディングはより容易になっています。


FSP_LANGUAGE_PREFERENCEの扱いについては、以下のブログ記事を参考にしました。

https://www.triology.de/en/blog-entries/database-applications-oracle-apex


言語セレクタをボタンにする方法については、以下の記事を参考にしました。

https://rimblas.com/blog/2020/06/style-your-apex_lang-emit_language_selector_list/


Oracle Application Expressのグローバリゼーション・コード