翻訳作業に使用するページを完成させます。
APEXコレクション名の設定
翻訳元と翻訳済みの文字列は、APEXコレクションに保存した上で対話グリッドから操作します。APEXコレクション名をSQLやPL/SQLに直書きすることを避けるため、置換文字列を設定します。
アプリケーション定義を開き、置換文字列G_TRANSLATE_COLLECTIONに対して、置換値としてTRANSLATEを設定します。
変更の適用をクリックします。
翻訳に使用するAPEXコレクションの名前はTRANSLATEになります。
ページ・アイテムとボタンの配置
ページ・デザイナでページ番号3のページTranslateを開きます。
このページで行う4つの作業を実行するボタンを作成します。ボタン名はそれぞれLOAD_FROM_SOURCE、STORE_AS_RESULT、LOAD_FROM_RESULT、BATCH_TRANSLATEとします。ラベルはボタン名から生成されたものを使用します。
作成したボタンを横一列に並べるため、LOAD_FROM_SOURCEを除くボタンのレイアウトの新規行の開始はOFFにします。
ページ・アイテムの識別の名前をP3_ID、タイプとして選択リストを選びます。ラベルはXLIFF Nameとします。アプリケーションとしては列IDの値が保存されていれば十分ですが、ユーザーには分かりやすくIDに代えて列Nameの値を表示します。
LOVのタイプはSQL問合せ、SQL問合せとして以下を記述します。追加値の表示、NULL値の表示は共にOFFです。
select name, id from cwr_xliff_files where id = :P3_ID
ユーザーが設定する値ではないため、読取り専用のタイプとして常時を指定します。
XLIFFの属性であるsource_langとtarget_langを画面に表示するために使用する、ページ・アイテムを作成します。
source_langを表示するページ・アイテムは識別の名前はP3_SOURCE_LANGとします。タイプとして表示のみを選択します。ラベルはSource Langとします。
設定のページの送信時に送信はOFFにし、ページ・アイテムP3_IDの右隣に配置されるよう、レイアウトの新規行の開始はOFFにします。
target_langを表示するページ・アイテムP3_TARGET_LANGも同様に作成します。ラベルはTarget Langです。
以上でボタンとページ・アイテムの配置は完了です。
対話グリッドの調整
対話グリッドで実施できる作業を、翻訳結果となる文字列の更新に限定します。
対話グリッドTranslateを選択し右ペインで属性を開きます。
編集の実行可能な操作から行の追加と行の削除のチェックを外します。また、ツールバーのコントロールにある保存ボタンのチェックを外します。
対話グリッドの列セレクタ(APEX$ROW_SELECTOR)と行アクション(APEX$ROW_ACTION)は不要なので、構成のビルド・オプションでCommented Out(コメント・アウト)します。
APEXコレクションを一意に指定する列はSEQ_IDです。列SEQ_IDを選択し、ソースの主キーをONにします。
列IDを更新することはないため、識別のタイプは表示のみとし、ソースの問合せのみをONにします。
翻訳元となる文字列を保持する列SOURCE_TEXTも更新することはないため、タイプを表示のみに変更し、ソースの問合せのみをONにします。
以上で対話グリッドの調整は完了です。
対話モード・レポートからリンク
アップロードしたXLIFFファイルの一覧から翻訳作業のページを開くために、リンクを作成します。
ページ番号1のXLIFF Filesのページを開き、対話モード・レポートの列Nameを選択します。
識別のタイプをリンクに変更し、リンクの属性を更新します。
ターゲットのタイプとしてこのアプリケーションのページを選択し、ページに3(ページTranslate)を指定します。
アイテムの設定として名前にP3_ID、値に#ID#を指定することで、選択したID値をページ・アイテムP3_IDに渡します。
以上でOKをクリックします。
プロセスの作成
対話グリッドに更新した翻訳済み文字列がAPEXコレクションに保存されるように、作成済みのプロセスTranslate - Save Interactive Grid Dataを更新します。
ページ・デザイナにてページ番号3のページTranslateを開きます。
左ペインでプロセス・ビューを表示し、プロセスTranslate - Save Interactive Grid Dataを選択します。
設定のターゲット・タイプをPL/SQL Codeに変更し、挿入/更新/削除するPL/SQLコードとして以下を記述します。この対話グリッドで許可されている操作は更新のみなので、更新に対応する処理のみ記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- begin | |
-- 対話グリッドで許可しているのは編集のみなのでROW_STATUSの確認は不要。 | |
-- if :APEX$ROW_STATUS = 'U' then | |
apex_collection.update_member_attribute( | |
p_collection_name => :G_TRANSLATE_COLLECTION | |
, p_seq => :SEQ_ID | |
, p_attr_number => 3 | |
, p_attr_value => :TARGET_TEXT | |
); | |
-- end if; | |
-- end; |
失われた更新の防止はOFF、行のロックはNoにします。APEXコレクションとして保持されているデータはAPEXセッションにアクセスが限定されているため、これらの保護は必要ありません。
サーバー側の条件としてボタン押下時にSTORE_AS_RESULTを指定します。
作成済みの4つのボタンの処理を行うプロセスを、それぞれ作成します。
最初にボタンが行う処理を記述したパッケージCWR_UTILを作成します。パッケージCWR_UTILのコードは以下になります。ボタン名がクリックされたときに、同名のプロシージャが呼び出されるようにします。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package CWR_UTIL as | |
/* | |
表CWR_XLIFF_FILESのXLIFF_SOURCEとして保存されているXLIFFファイルを | |
APEXコレクションに取り込む。 | |
*/ | |
procedure load_from_source ( | |
p_id in number | |
, p_source_lang out varchar2 | |
, p_target_lang out varchar2 | |
, p_collection_name in varchar2 default 'TRANSLATE' | |
); | |
/* | |
表CWR_XLIFF_FILESのXLIFF_RESULTとして保存されているXLIFFファイルを | |
APEXコレクションに取り込む。 | |
*/ | |
procedure load_from_result ( | |
p_id in number | |
, p_source_lang out varchar2 | |
, p_target_lang out varchar2 | |
, p_collection_name in varchar2 default 'TRANSLATE' | |
); | |
/* | |
APEXコレクションに保持されている翻訳結果をXLIFF_SOURCEに適用し、 | |
XLIFF_RESULTに保存する。 | |
*/ | |
procedure store_as_result ( | |
p_id in number | |
, p_collection_name in varchar2 default 'TRANSLATE' | |
); | |
/* | |
APEXコレクションに保持されている翻訳前の文字列を、表CWR_MESSAGESの | |
内容を使って、一括で翻訳する。 | |
*/ | |
procedure batch_translate ( | |
p_source_lang in varchar2 | |
, p_target_lang in varchar2 | |
, p_collection_name in varchar2 default 'TRANSLATE' | |
); | |
end; | |
/ | |
create or replace package body cwr_util | |
as | |
/* | |
XLIFFファイルよりsource_langとtarget_langを取り出す。 | |
*/ | |
procedure get_source_and_target_lang( | |
p_xmlDoc in dbms_xmldom.domdocument | |
,p_source_lang out varchar2 | |
,p_target_lang out varchar2 | |
) | |
as | |
l_attrs dbms_xmldom.domnamednodemap; | |
begin | |
/* file要素の属性より、ソース言語とターゲット言語を取得する。 */ | |
l_attrs := dbms_xmldom.getAttributes( | |
dbms_xmldom.item( | |
dbms_xmldom.getElementsByTagName(p_xmlDoc, 'file'), | |
0) | |
); | |
p_source_lang := dbms_xmldom.getNodeValue( | |
dbms_xmldom.getNamedItem(l_attrs, 'source-language') | |
); | |
p_target_lang := dbms_xmldom.getNodeValue( | |
dbms_xmldom.getNamedItem(l_attrs, 'target-language') | |
); | |
end get_source_and_target_lang; | |
/* | |
BLOBとして渡されるXLIFF(XML)から、翻訳前の文字列と翻訳済みの文字列から | |
APEXコレクションを作成する。 | |
*/ | |
procedure load_from_blob ( | |
p_blob in blob -- XLIFFの内容 | |
, p_source_lang out varchar2 | |
, p_target_lang out varchar2 | |
, p_collection_name in varchar2 | |
) | |
as | |
l_xml xmltype; | |
l_xmlDoc dbms_xmldom.domdocument; | |
l_transUnits dbms_xmldom.domnodelist; | |
l_transUnit dbms_xmldom.domelement; | |
l_id varchar2(80); | |
l_source_text cwr_messages.message_text%type; | |
l_target_text cwr_messages.message_text%type; | |
begin | |
/* BLOBよりDOMとして操作可能なXML文書を作成する。 */ | |
l_xml := xmltype.createXML(p_blob, NLS_CHARSET_ID('AL32UTF8'), null); | |
l_xmlDoc := dbms_xmldom.newDOMDocument(l_xml); | |
get_source_and_target_lang( | |
p_xmlDoc => l_xmlDoc | |
,p_source_lang => p_source_lang | |
,p_target_lang => p_target_lang | |
); | |
/* XMLに含まれるtrans-unitの要素を全て取り出し、APEXコレクションに投入する。 */ | |
apex_collection.create_or_truncate_collection(p_collection_name); | |
l_transUnits := dbms_xmldom.getElementsByTagName(l_xmlDoc, 'trans-unit'); | |
for i in 0..dbms_xmldom.getlength(l_transUnits)-1 | |
loop | |
l_transUnit := dbms_xmldom.makeElement( | |
dbms_xmldom.item(l_transUnits,i) | |
); | |
-- trans-unitのidを取り出す。 | |
l_id := dbms_xmldom.getAttribute(l_transUnit, 'id'); | |
-- trans-unitに含まれる要素source(翻訳対象の文字列)の値を取り出す。 | |
l_source_text := dbms_xmldom.getNodeValue( | |
dbms_xmldom.getFirstChild( | |
dbms_xmldom.item( | |
dbms_xmldom.getChildrenByTagName(l_transUnit, 'source'), | |
0 | |
) | |
) | |
); | |
-- trans-unitに含まれる要素target(翻訳済みの文字列)の値を取り出す。 | |
l_target_text := dbms_xmldom.getNodeValue( | |
dbms_xmldom.getFirstChild( | |
dbms_xmldom.item( | |
dbms_xmldom.getChildrenByTagName(l_transUnit, 'target'), | |
0 | |
) | |
) | |
); | |
-- 取り出した値をAPEXコレクションに保存する。 | |
apex_collection.add_member( | |
p_collection_name => p_collection_name | |
, p_c001 => l_id | |
, p_c002 => l_source_text | |
, p_c003 => l_target_text | |
); | |
end loop; | |
end load_from_blob; | |
procedure load_from_source( | |
p_id in number | |
, p_source_lang out varchar2 | |
, p_target_lang out varchar2 | |
, p_collection_name in varchar2 | |
) | |
as | |
l_blob blob; | |
begin | |
/* 列XLIFF_SOURCEからAPEXコレクションを作成する。 */ | |
select xliff_source into l_blob from cwr_xliff_files where id = p_id; | |
load_from_blob( | |
p_blob => l_blob | |
, p_source_lang => p_source_lang | |
, p_target_lang => p_target_lang | |
, p_collection_name => p_collection_name | |
); | |
end load_from_source; | |
procedure load_from_result( | |
p_id in number | |
, p_source_lang out varchar2 | |
, p_target_lang out varchar2 | |
, p_collection_name in varchar2 | |
) | |
as | |
l_blob blob; | |
begin | |
/* 列XLIFF_RESULTからAPEXコレクションを作成する。 */ | |
select xliff_result into l_blob from cwr_xliff_files where id = p_id; | |
load_from_blob( | |
p_blob => l_blob | |
, p_source_lang => p_source_lang | |
, p_target_lang => p_target_lang | |
, p_collection_name => p_collection_name | |
); | |
end load_from_result; | |
procedure store_as_result ( | |
p_id in number | |
, p_collection_name in varchar2 | |
) | |
as | |
l_xml xmltype; | |
l_blob blob; | |
l_blob_result blob; | |
l_xmlDoc dbms_xmldom.domdocument; | |
l_source_lang varchar2(80); | |
l_target_lang varchar2(80); | |
l_transUnits dbms_xmldom.domnodelist; | |
l_transUnit dbms_xmldom.domelement; | |
l_id varchar2(80); | |
l_target dbms_xmldom.domnode; | |
l_target_text cwr_messages.message_text%type; | |
begin | |
/* XLIFF_SOURCEを取り出し、一時LOBにコピーした上でDOMを生成する。 */ | |
select xliff_source into l_blob from cwr_xliff_files where id = p_id; | |
dbms_lob.createtemporary(l_blob_result, true); | |
dbms_lob.copy( | |
dest_lob => l_blob_result | |
, src_lob => l_blob | |
, amount => dbms_lob.lobmaxsize | |
); | |
l_xml := xmltype.createXML(l_blob_result, NLS_CHARSET_ID('AL32UTF8'), null); | |
l_xmlDoc := dbms_xmldom.newDOMDocument(l_xml); | |
get_source_and_target_lang( | |
p_xmlDoc => l_xmlDoc | |
,p_source_lang => l_source_lang | |
,p_target_lang => l_target_lang | |
); | |
/* ソースとなるXLIFFのtrans-unitを全て取り出し、APEXコレクションに保持されている翻訳済み | |
* 文字列でtargetを置き換える。 | |
*/ | |
l_transUnits := dbms_xmldom.getElementsByTagName(l_xmlDoc, 'trans-unit'); | |
for i in 0..dbms_xmldom.getlength(l_transUnits)-1 | |
loop | |
l_transUnit := dbms_xmldom.makeElement( | |
dbms_xmldom.item(l_transUnits,i) | |
); | |
l_id := dbms_xmldom.getAttribute(l_transUnit, 'id'); | |
select c003 into l_target_text from apex_collections where collection_name = p_collection_name and c001 = l_id; | |
l_target := dbms_xmldom.getFirstChild( | |
dbms_xmldom.item( | |
dbms_xmldom.getChildrenByTagName(l_transUnit, 'target'), | |
0 | |
) | |
); | |
dbms_xmldom.setNodeValue(l_target, l_target_text); | |
end loop; | |
/* 置き換えたDOMをBLOBに変換し、XLIFF_RESULTに保存する。 */ | |
update cwr_xliff_files | |
set xliff_result = l_xml.getblobval(NLS_CHARSET_ID('AL32UTF8')) | |
,xliff_result_filename = | |
replace(xliff_source_filename,'.xlf','_translated.xlf') | |
,xliff_result_mimetype = xliff_source_mimetype | |
,xliff_result_charset = xliff_source_charset | |
,xliff_result_lastupd = sysdate | |
,source_lang = l_source_lang | |
,target_lang = l_target_lang | |
where id = p_id; | |
end store_as_result; | |
procedure batch_translate( | |
p_source_lang in varchar2 | |
, p_target_lang in varchar2 | |
, p_collection_name in varchar2 | |
) | |
as | |
l_target_text cwr_messages.message_text%type; | |
begin | |
/* APEXコレクションのC002 - 翻訳対象文字列 - に一致する文字列を表CWR_MESSAGESより探し、 | |
* 一致する文字列があれば、その翻訳結果でC003 - 翻訳済み文字列 - を置き換える。 | |
* 翻訳対象文字列に複数の翻訳済み文字列が存在する場合は(選択する基準がないので)、最初に見つけた | |
* 結果を採用する。 | |
*/ | |
for c in (select seq_id, c001, c002 from apex_collections where collection_name = p_collection_name) | |
loop | |
begin | |
/* 翻訳済み文字列を探す。 */ | |
select t.message_text into l_target_text | |
from cwr_messages s join cwr_messages t on s.name = t.name | |
where s.message_language = p_source_lang | |
and t.message_language = p_target_lang | |
and s.message_text = c.c002 | |
order by s.name fetch first 1 rows only; | |
/* APEXコレクションのC003のみを更新する。 */ | |
apex_collection.update_member_attribute( | |
p_collection_name => p_collection_name | |
, p_seq => c.seq_id | |
, p_attr_number => 3 | |
, p_attr_value => l_target_text | |
); | |
exception | |
when others then | |
null; -- 翻訳文が見つからなければ、何も変更しない。 | |
end; | |
end loop; | |
end batch_translate; | |
end cwr_util; | |
/ |
パッケージの作成には、SQLワークショップのSQLスクリプトなどを使用できます。Autonomous Databaseであれば、この記事による実行も可能です。
プロセスを作成し、識別の名前をLoad From Sourceとします。タイプとしてAPIの呼出しを選択します。
設定のパッケージとしてCWR_UTILを選択し、プロシージャまたはファンクションとしてLOAD_FROM_SOURCEを選択します。サーバー側の条件のボタン押下時にLOAD_FROM_SOURCEを選択し、このボタンが押された時に実行されるようにします。
パラメータのp_id、p_source_lang、p_target_langには、(p_またはP3_を除いた名前が一致するため)デフォルトでページ・アイテムP3_ID、P3_SOURCE_LANG、P3_TARGET_LANGが設定されます。
パラメータp_collection_nameのタイプはAPIデフォルトになっているので(APIのデフォルトはTRANSLATEになっているため、実際はこのままでも動作します)、値のタイプをアイテムに変更し、アイテムとして置換文字列として設定されているG_TRANSLATE_COLLECTIONを指定します。
同様の手順でプロセスStore As Resultを作成します。パラメータのp_collection_nameも同じくアイテムとしてG_TRANSLATE_COLLECTIONを指定します。
プロセスStore As ResultはプロセスTranslate - Save Interactive Grid Dataより下に配置します。表CWR_XLIFF_FILESの列XLIFF_RESULTに翻訳済みのデータを保存する前に、対話グリッドの更新データがAPEXコレクションに保存されている必要があります。
プロセスLoad From Resultも同様に作成します。
最後にプロセスBatch Translateを作成します。
以上でプロセスの作成は完了です。
翻訳テスト
一括翻訳をテストするために以下のINSERT文を実行し、表CWR_MESSAGESにデータを用意します。
SQLワークショップのSQLスクリプトなどを使用して実行します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
insert into cwr_messages(name, message_language, message_text) values('WR_BEEF_BOWL','en','Beef Bowl'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_BEEF_BOWL','ja','牛丼'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_BEEF_BOWL','ko','소고기 덮밥'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_BEEF_BOWL','zh-cn','牛肉碗'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_FRIED_SOURCE_NOODLES','en','Fried Source Noodles'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_FRIED_SOURCE_NOODLES','ja','炸醤麺'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_FRIED_SOURCE_NOODLES','ko','짜장면'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_FRIED_SOURCE_NOODLES','zh-cn','炸醬麵'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_GINSENG_CHICKEN_SOUP','en','Ginseng Chicken Soup'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_GINSENG_CHICKEN_SOUP','ja','参鶏湯'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_GINSENG_CHICKEN_SOUP','ko','삼계탕'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_GINSENG_CHICKEN_SOUP','zh-cn','蔘鷄湯'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_HAMBURGER','en','Hamburger'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_HAMBURGER','ja','ハンバーガー'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_HAMBURGER','ko','햄버거'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_HAMBURGER','zh-cn','汉堡包'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_TITLE','en','World Diner'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_TITLE','ja','世界食堂'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_TITLE','ko','식당세계'); | |
insert into cwr_messages(name, message_language, message_text) values('WR_TITLE','zh-cn','世界餐厅'); | |
commit; |
実行が正常に完了していると、Messagesのページより挿入された翻訳を確認することができます。
以上でテストの準備ができました。
最初の記事に添付されているworld-diner.xlfををダウンロードし、XLIFF Filesの画面でアップロードします。
ボタンLoad From Sourceをクリックして、表CWR_XLIFF_FILESの列XLIFF_SOURCEとして保存されているXLIFFファイルの内容をAPEXコレクションにロードします。
列Target Textをひとつひとつ手入力で更新することもでできますが、ここではボタンBatch Translateをクリックして、表CWR_MESSAGESの内容を使って一括翻訳します。
翻訳結果がAPEXコレクションに反映されます。ボタンStore As Resultをクリックし、表CWR_XLIFF_FILESの列XLIFF_RESULTにXLIFFファイルとして保存します。
XLIFF Filesの画面に戻るとXLIFF Resultとして、ファイルのダウンロードができるようになっています。これをクリックして翻訳済みのXLIFFファイルをダウンロードして内容を確認します。
ダウンロードされたファイルの名前はworld-diner_ja_en.xlfになります。ファイルを開いて内容が翻訳されていることを確認します。
以上で翻訳作業を行うページは完成です。
次の記事で、DeepLを使った翻訳を追加します。
続く