メタデータAPIを使って翻訳データを抽出してみた

はじめに

皆さんはSalesforceの翻訳データを見るときに何を使っていますか?
一般的にはSalesforceの設定画面から言語を選び、コンポーネントを選んで言語ごとに選ぶか、エクスポートしてstfファイルを確認するケースが多いかと思います。

国際化対応をしていくと、各言語の翻訳データを一覧で見ることが多々あるので、APEXでどうにかできないか色々調べてみました。

翻訳データ抽出方法

翻訳データはメタデータAPIを使うことで抽出できそうです。
※メタデータ API 開発者ガイド を参照
なので今回はメタデータAPIをつかって一覧表示をしてみたいと思います。

全体の流れ

1.事前準備
2.メタデータからマスター値の取得
3.メタデータから翻訳データの取得

という形で処理を進めます。

1.事前準備

まずは、メタデータ取得用、画面表示用などに使うインナークラス系を定義します。
各クラスの定義

・LangTypePickParam マスターラベルと翻訳値の選択リスト値を格納するクラス
・MetadataPort メタデータを読み込むためのクラス
・ReadResult readMetadata コールの結果情報インターフェイス
・ReadResponseElement メタデータのResponseインターフェイス
・SessionHeader_element セッションヘッダクラス
・ReadMetadata_element メタデータコンポーネント読み取りクラス
・ReadTranslationsResponse_element ReadTranslationsResponse_element
・ReadTranslationsResult 結果情報を取得クラス
・Metadata すべてのメタデータ型の基本クラス
・Translations 使用言語翻訳クラス
・GlobalPicklist グローバル選択リストメタデータクラス
・GlobalPicklistValue グローバル選択リスト値クラス
・GlobalPicklistTranslation グローバル選択リストの翻訳

今回未使用だがメタデータ取得時に定義しておかなければいけないクラス
・CustomApplicationTranslation カスタムアプリケーション翻訳クラス
・CustomLabelTranslation カスタム表示ラベル翻訳クラス
・CustomTabTranslation カスタムタブ翻訳クラス
・PicklistValueTranslation 選択リスト値翻訳クラス
・GlobalQuickActionTranslation グローバルなクイックアクション翻訳クラス

インナークラスサンプルコード

    public class LangTypePickParam {
public String master {get; private set;}
public String ja {get; private set;}
public String en_US {get; private set;}
public String zh_CN {get; private set;}
}
public class MetadataPort {
public String endpoint_x = URL.getSalesforceBaseUrl().toExternalForm() + '/services/Soap/m/37.0';
public Map<String,String> inputHttpHeaders_x;
public Map<String,String> outputHttpHeaders_x;
public String clientCertName_x;
public String clientCert_x;
public String clientCertPasswd_x;
public Integer timeout_x;
public SessionHeader_element SessionHeader;
private String SessionHeader_hns = 'SessionHeader=http://soap.sforce.com/2006/04/metadata';
private String AllOrNoneHeader_hns = 'AllOrNoneHeader=http://soap.sforce.com/2006/04/metadata';
private String[] ns_map_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata', 'Translation'};
public ReadResult ReadMetadata(String type_x,String[] fullNames) {
ReadMetadata_element request_x = new ReadMetadata_element();
request_x.type_x = type_x;
request_x.fullNames = fullNames;
ReadResponseElement response_x;
Map<String, ReadResponseElement> response_map_x = new Map<String, ReadResponseElement>();
response_map_x.put('response_x', response_x);
WebServiceCallout.invoke(
this,
request_x,
response_map_x,
new String[]{endpoint_x,
'',
'http://soap.sforce.com/2006/04/metadata',
'readMetadata',
'http://soap.sforce.com/2006/04/metadata',
'readMetadataResponse',
'Translation2Controller.read' + type_x + 'Response_element'}
);
response_x = response_map_x.get('response_x');
return response_x.getResult();
}
}
public interface ReadResult {
Metadata[] getRecords();
}
public interface ReadResponseElement {
ReadResult getResult();
}
public class SessionHeader_element {
public String sessionId;
private String[] sessionId_type_info = new String[]{'sessionId','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'sessionId'};
}
public class ReadMetadata_element {
public String type_x;
public String[] fullNames;
private String[] type_x_type_info = new String[]{'type','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] fullNames_type_info = new String[]{'fullNames','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'type_x','fullNames'};
}
public class ReadTranslationsResponse_element implements ReadResponseElement {
public ReadTranslationsResult result;
public ReadResult getResult() { return result; }
private String[] result_type_info = new String[]{'result','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'result'};
}
public class ReadTranslationsResult implements ReadResult {
public Translations[] records;
public Metadata[] getRecords() { return records; }
private String[] records_type_info = new String[]{'records','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'records'};
}
// 以下すべてのクラスは、ドキュメント「メタデータAPI開発者ガイド」に記載
/**
* すべてのメタデータ型の基本クラス
*/
public virtual class Metadata {
public String fullName;
}
/**
* Metadata メタデータ型を拡張し、そのfullName 項目を継承します。
*/
public class Translations extends Metadata {
public String type = 'Translations';
public String fullName;
private String[] fullName_type_info = new String[]{'fullName','http://soap.sforce.com/2006/04/metadata',null,'0','1','false'};
// カスタムアプリケーション翻訳のリスト
public CustomApplicationTranslation[] customApplications;
// カスタム表示ラベル翻訳のリスト
public CustomLabelTranslation[] customLabels;
// カスタムタブ翻訳のリスト
public CustomTabTranslation[] customTabs;
// グローバル選択リストのリスト
public GlobalPicklistTranslation[] globalPicklists;
// グローバル (オブジェクト固有ではない) クイックアクションのリスト
public GlobalQuickActionTranslation[] quickActions;
private String[] customApplications_type_info = new String[]{'customApplications','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] customLabels_type_info = new String[]{'customLabels','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] customTabs_type_info = new String[]{'customTabs','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] globalPicklists_type_info = new String[]{'globalPicklists','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] quickActions_type_info = new String[]{'quickActions','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] type_att_info = new String[]{'xsi:type'};
private String[] field_order_type_info = new String[]{'fullName', 'customApplications','customDataTypeTranslations','customLabels','customPageWebLinks','customTabs','globalPicklists','quickActions','reportTypes','scontrols'};
}
/**
* グローバル選択リストのメタデータ
*/
public class GlobalPicklist extends Metadata {
public String type = 'GlobalPicklist';
public String fullName;
private String[] fullName_type_info = new String[]{'fullName','http://soap.sforce.com/2006/04/metadata',null,'0','1','false'};
public String description;
public GlobalPicklistValue[] globalPicklistValues;
public String masterLabel;
public Boolean sorted;
private String[] description_type_info = new String[]{'description','http://soap.sforce.com/2006/04/metadata',null,'0','1','false'};
private String[] globalPicklistValues_type_info = new String[]{'globalPicklistValues','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] masterLabel_type_info = new String[]{'masterLabel','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] sorted_type_info = new String[]{'sorted','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] type_att_info = new String[]{'xsi:type'};
private String[] field_order_type_info = new String[]{'fullName', 'description','globalPicklistValues','masterLabel','sorted'};
}
public virtual class GlobalPicklistValue extends Metadata {
public String color;
public Boolean default_x;
public String description;
public Boolean isActive;
}
public class CustomApplicationTranslation {
public String label;
public String name;
private String[] label_type_info = new String[]{'label','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] name_type_info = new String[]{'name','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'label','name'};
}
public class CustomLabelTranslation {
public String label;
public String name;
private String[] label_type_info = new String[]{'label','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] name_type_info = new String[]{'name','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'label','name'};
}
public class CustomTabTranslation {
public String label;
public String name;
private String[] label_type_info = new String[]{'label','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] name_type_info = new String[]{'name','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'label','name'};
}
public class GlobalPicklistTranslation {
public String name;
public PicklistValueTranslation[] picklistValues;
private String[] name_type_info = new String[]{'name','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] picklistValues_type_info = new String[]{'picklistValues','http://soap.sforce.com/2006/04/metadata',null,'0','-1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'name','picklistValues'};
}
public class PicklistValueTranslation {
public String masterLabel;
public String translation;
private String[] masterLabel_type_info = new String[]{'masterLabel','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] translation_type_info = new String[]{'translation','http://soap.sforce.com/2006/04/metadata',null,'0','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'masterLabel','translation'};
}
public class GlobalQuickActionTranslation {
public String label;
public String name;
private String[] label_type_info = new String[]{'label','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] name_type_info = new String[]{'name','http://soap.sforce.com/2006/04/metadata',null,'1','1','false'};
private String[] apex_schema_type_info = new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'label','name'};
}

2.メタデータからマスター値の取得

まずはメタデータから選択リストのマスター値の取得をします。

        Translations master = 
(Translations) service.readMetadata('Translations',langType).getRecords()[0];
LangTypePickParam ltpp;
List<LangTypePickParam> LangTypePickParamList;
// 選択リストの項目名の種類だけループ
for(GlobalPicklistTranslation GlobalPicklistTranslation : master.globalPicklists) {
LangTypePickParamList = new List<LangTypePickParam>();
// 選択リスト値の値の数分だけループ
for(PicklistValueTranslation pickListValue : GlobalPicklistTranslation.picklistValues) {
ltpp = new LangTypePickParam();
ltpp.Master = pickListValue.masterLabel;
LangTypePickParamList.add(ltpp);
}
if(!pickListMap.containsKey(GlobalPicklistTranslation.name)) {
this.pickListMap.put(GlobalPicklistTranslation.name, LangTypePickParamList);
this.pickListDecisionMap.put(GlobalPicklistTranslation.name, false);
}
}

先頭の
Translations master =
 (Translations) service.readMetadata('Translations',langType).getRecords()[0];
の処理のなかで、WebService経由でメタデータを取得しています。
※このデータは翻訳データが大量に入っているのでグローバル選択リストのデータのみを更新対象としています。

3.メタデータから翻訳データの取得

各言語毎にメタデータを読み取り、表示用PickListMapに追記する形で格納していきます。
この際、翻訳データが存在しないグローバル選択リストを表示させないために、翻訳値が存在しないデータはpickListDecisionMapにも格納します。

        for(Integer i = 0 ; i < langType.size() ; i++) {
// メタデータから翻訳に関係する情報を取得する
Translations translation;
translation = (Translations) service.readMetadata('Translations',langType).getRecords()[i];
// 選択リストの項目名の種類だけループ
for(GlobalPicklistTranslation GlobalPicklistTranslation : translation.globalPicklists) {
if(pickListMap.containsKey(GlobalPicklistTranslation.name)) {
transDecisionFlag = false;
for(Integer j = 0 ; j < GlobalPicklistTranslation.picklistValues.size() ; j++) {
ltpp = new LangTypePickParam();
ltpp = pickListMap.get(GlobalPicklistTranslation.name).get(j);
if(langType[i] == 'ja') {
ltpp.ja = GlobalPicklistTranslation.picklistValues[j].translation;
if(!String.isBlank(ltpp.ja)) {
transDecisionFlag = true;
}
} else if(langType[i] == 'en_US') {
ltpp.en_US = GlobalPicklistTranslation.picklistValues[j].translation;
if(!String.isBlank(ltpp.en_US)) {
transDecisionFlag = true;
}
} else if(langType[i] == 'zh_CN') {
ltpp.zh_CN = GlobalPicklistTranslation.picklistValues[j].translation;
if(!String.isBlank(ltpp.zh_CN)) {
transDecisionFlag = true;
}
}
this.pickListMap.get(GlobalPicklistTranslation.name).set(j,ltpp);
if(transDecisionFlag) {
this.pickListDecisionMap.put(GlobalPicklistTranslation.name, transDecisionFlag);
}
}
}
}
}

こうして、PickListMapに表示用の選択リストの翻訳データが格納されました。

これをページ側で表示すると以下のように表示されます。
画面

<apex:page controller="Translation2Controller" cache="false" id="thePage">
<apex:outputPanel id="errPnl">
<apex:pageMessages escape="false" />
</apex:outputPanel>
<apex:form >
<apex:actionStatus onstart="blockUI()" onstop="unblockUI()" id="loading"/>
<apex:pageBlock title="グローバル選択リスト">
<table class="tbl">
<caption><b>グローバル選択リスト翻訳テーブル</b></caption>
<tr>
<th>グローバル選択リスト名</th>
<th>MasterLabel</th>
<th>日本語</th>
<th>英語</th>
<th>中国語</th>
</tr>
<apex:repeat value="{!pickListMap}" var="key">
<apex:outputPanel rendered="{!pickListDecisionMap[key]}">
<tr>
<td bgcolor="#FFFFFF">
{!key}
</td>
<td bgcolor="#FFFFFF">
<apex:repeat value="{!pickListMap[key]}" var="val">
<apex:outputText value="{!val.master}<br/>" escape="false"/>
</apex:repeat>
</td>
<td bgcolor="#FFFFFF">
<apex:repeat value="{!pickListMap[key]}" var="val">
<apex:outputText value="{!val.ja}<br/>" escape="false"/>
</apex:repeat>
</td>
<td bgcolor="#FFFFFF">
<apex:repeat value="{!pickListMap[key]}" var="val">
<apex:outputText value="{!val.en_US}<br/>" escape="false"/>
</apex:repeat>
</td>
<td bgcolor="#FFFFFF">
<apex:repeat value="{!pickListMap[key]}" var="val">
<apex:outputText value="{!val.zh_CN}<br/>" escape="false"/>
</apex:repeat>
</td>
</tr>
</apex:outputPanel>
</apex:repeat>
</table>
</apex:pageBlock>
</apex:form>
</apex:page>

まとめ

メタデータAPIを使って、無事APEX内で翻訳データを抜き取って表示することができました。
これを応用すれば、このデータをカスタムオブジェクトにマスター化することで、データ連携時に複数の翻訳されているデータを他システムに連携したり、Apexで特定の言語で翻訳したデータをレコードに書き込むなどに使えるのではないかなあと思います。