今日は多言語主義についてお話します。それで、それは何ですか?
多言語化、つまり
国際化は、プログラム ロジックを変更せずに複数の言語に適応できるアプリケーション開発の一部です。状況を考えてみましょう。たとえば、ロシア語、英語、スペイン語などの言語が話されている多数の国の居住者向けに、大規模な商社用の Web アプリケーションを作成しているとします。すべてのユーザーにとって便利なものにする必要があります。考え方は次のとおりです。ロシア語を話す読者はデータをロシア語で見ることができ、アメリカ人は英語で資料を読む方が快適で、スペイン人はスペイン語で読むことができます (予想外ですよね?)
今日は
いくつかの国際化モデルを考えてみましょう。そしてそのうちの 1 つ (これが私が最も気に入っています:) Java での実装を見てみましょう。モルモットとして、今日は映画のデータプレートを用意します。あまりひねくれすぎないようにしましょう。だから講演者はあまり多くありません。たとえば、それはOKです。そして、映画(監督 - エキストラ)の名前を翻訳します。
1. 各言語の翻訳を含む表
このモデルの本質は、各言語がデータベース内に個別のテーブルを持ち、そのテーブルには翻訳が必要なすべてのセルが含まれているということです。この方法の欠点は、新しい言語を追加するたびに、新しいテーブルを追加する必要があることです。つまり、私たちの顧客が非常に順調に業績を上げており、アプリケーションを世界の多くの国 (実際には言語) に拡大していると想像してください。つまり、舌ごとに 1 錠ずつ追加する必要があります。その結果、データベースの半分またはほぼ全体が補助変換テーブルで構成されることになります。
映画自体の概略図: 変換テーブル:
2. ワン・フォー・オール
特定のモデルに属する各テーブルに、言語プレートの識別子を含むフィールドが追加されます。したがって、データベースには、このテーブルと翻訳も含まれています。問題は、1 つのオブジェクトが複数の翻訳 (言語) に対応できることです。その結果、エンティティの重複が発生し、ロジックが非常に混乱して複雑になるため、これは良くありません。
UML について見ていきます: テーブル ムービー: テーブル言語:
3. 舌ごとの列
テーブル内の言語ごとに、列ごとに個別の翻訳列が作成されます。このアプローチの欠点は、やはり、多数の言語を追加すると、そのたびにデータベース構造を変更する必要があり、これは悪いアプローチであると考えられることです。また、国際化を求める兆候がどれほど大きくなるか想像してみてください。サポートされる言語の数が事前にわかっていて、その数が多すぎず、各モデルがすべての言語バリエーションに存在する必要があるモデルを検討する価値があるかもしれません。
UML: 包括的な表:
4. 外部翻訳
このオプションは、外部ツール (Google 翻訳、Bing 翻訳など) を接続することで実装されます。できるだけ多くの訪問者に情報を提供する必要がある場合に使用され、その情報は大量にあります。とてもたくさんあります。この場合、すべての言語の情報をデータベースに直接保存するのではなく、動的に翻訳することを決定できます。ただし、機械翻訳の品質には改善の余地が多々あることを覚えておく価値があります。このオプションは、(各出版物を翻訳するためのリソースがない場合には) 非常に経済的であると考えられます。正しい翻訳に関してよくある問題は、その言語をよく知らない翻訳者が単語の間違った意味を選んでユーザーを混乱させ、ユーザーがボタンに書かれている意味を独自に理解することを強いられることです。文章を正しく翻訳するだけでなく、その意味を特定の言語や国籍に当てはめることも重要です。開発者は、一部の言語の性別に関して多くの問題を抱えています。ユーザーの性別に応じてコード内のフレーズを複製する必要があり、また、名詞に性別があるだけでなく、形容詞や動詞の語形変化も考慮する必要があります。アプリケーションで英語以外の言語を選択した場合、選択した言語の単語とともに未翻訳の要素が残っている場合があります。複数の言語が表示され、すべてがごちゃ混ぜになってユーザーがアプリケーションを理解できない一種のバビロンになる場合はさらに悪くなります。例:
https://cloud.google.com/translate/
5. アプリケーションレベルのサポートファイル
翻訳を保存するために別のファイルが作成されます。舌ごとにファイル 1 つ、または錠剤ごとに舌ごとに 1 つのファイル(細かい粉砕)を使用できます。このオプションは、これらのファイルに多くのテキストを保存できるため、テーブルやデータベース自体が肥大化しないという理由でよく使用されます。もう 1 つの便利な点は、これらのフィールドについてデータベースを参照する必要がなく、要求された言語に応じてコード内のファイルを動的に置き換えることができることです。結果として、このファイルは辞書として機能し、キーは言語、値はテキストになります。ただし、以下の「.properties」形式に限定されるわけではなく、これらのファイル形式は JSON、XML など、さまざまに異なります。欠点は、この場合、データベースの正規化が大幅に低下することです。また、データの整合性はデータベースのみに依存するのではなく、シリアル化メカニズムにも依存します。
このトピックに関する優れた記事 翻訳付きの辞書ファイルの例:
6. 各テーブルの補助変換テーブル
私の意見では、最も柔軟なソリューションです。このアプローチの本質は、言語ごとに別のテーブルを作成することです。問題のテーブルに翻訳の可能性を実装する必要がある場合、言語テーブルとのリンクが作成され、リンク テーブルには言語 ID、要素 ID、および翻訳を含む列が含まれます。思っているほど怖くないです。このアプローチにより、サポートされる言語のかなり柔軟な拡張が可能になります。詳しく見てみましょう。
UML: ムービー付きの表: 言語の表: 翻訳の表: そして、上で述べたように、Java コードでのオプションの 1 つの実装を見てみましょう (ご存知のとおり、これが最後のオプションになります)。アプリケーション自体にはそのようなことはありません。コントローラーから dao レイヤーに進みます。create メソッドを見ていきます。例としてはこれで十分です。それでは行きましょう)) 私たちの本質は映画です:
@Builder
@Getter
public class Movie {
private Long id;
private String producer;
}
最初のテーブルのモデルを実装するだけで、何も興味深いものはありません。
dto (データ転送オブジェクト) コンバーターを備えたコントローラー:
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/cities")
public class MovieController {
private final MovieService movieService;
@PostMapping
public ResponseEntity<moviedto> create(MovieDTO movieDTO) {
return new ResponseEntity<>(toDTO(movieService.create(fromDTO(movieDTO), movieDTO.getNameTranslations()), movieDTO.getNameTranslations()), HttpStatus.CREATED);
}
private Movie fromDTO(MovieDTO dto) {
return Movie.builder()
.id(dto.getId())
.producer(dto.getProducer())
.build();
}
private MovieDTO toDTO(Movie movie, Map<string, string=""> nameTranslation) {
return MovieDTO.builder()
.id(movie.getId())
.producer(movie.getProducer())
.nameTranslations(nameTranslation)
.build();
}
}
DTO では、翻訳をマップとして渡します。キーは言語の略語、値は翻訳値 (映画名) です。
DTO:
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String producer;
@JsonProperty("nameTranslations")
private Map<String, String> nameTranslations;
}
ここでは、上で書いたように dto クラス自体が翻訳用にマップされており、残りのフィールドは Movie モデルの表示です。
映画サービスに移りましょう:
public interface MovieService {
Movie create(Movie movie, Map nameList);
}
その実装:
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieDAO movieDAO;
private LanguageService languageService;
@Override
public Movie create(Movie movie, Map<string, string=""> nameList) {
movieDAO.create(movie);
Map<Long, String> map = new HashMap<>();
nameList.forEach((x, y) -> map.put(languageService.getIdByLangCode(x), y));
movieDAO.createTranslator(movie.getId(), map);
return movie;
}
}
ここでは、比較的サードパーティの LanguageService サービスを使用して、略語によって言語 ID を取得しています。そして、この識別子を使用して、変換を (これもマップの形式で) 接続テーブルに保存します。
DAO を見てみましょう。
public interface MovieDAO {
void create(Movie movie);
void createTranslator(Long movieId, Map<Long,String> nameTranslations);
}
実装:
@RequiredArgsConstructor
@Repository
public class MovieDAOImpl implements MovieDAO {
private final JdbcTemplate jdbcTemplate;
private static final String CREATE_MOVIE = "INSERT INTO movies(id, producer) VALUES(?, ?)";
private static final String CREATE_TRANSLATOR = "INSERT INTO movies_translator(movies_id, language_id, name) VALUES(?, ?, ?)";
@Override
public void create(Movie movie) {
jdbcTemplate.update(CREATE_MOVIE, movie.getId(), movie.getProducer());
}
@Override
public void createTranslator(Long movieId, Map<Long, String> nameTranslations) {
nameTranslations.forEach((x, y) -> jdbcTemplate.update(CREATE_TRANSLATOR, movieId, x, y));
}
}
ここでは、本質とそのための言語(辞書)の保存が見られます。そして、はい、ここでは Spring JDBC が使用されています。より透過的であるため、初心者にとっては Spring JDBC の方が好ましいと思います。「サードパーティ」サービスに移りましょう。
言語サービス:
public interface LanguageService {
Long getIdByLangCode(String lang);
}
実装:
@Service
@RequiredArgsConstructor
public class LanguageServiceImpl implements LanguageService {
private final LanguageDAO languageDAO;
@Override
public Long getIdByLangCode(String lang) {
return languageDAO.getIdByLangCode(lang);
}
}
特に何もせず、短縮名で検索してください。
ダオ:
public interface LanguageDAO {
Long getIdByLangCode(String lang);
}
実装:
@RequiredArgsConstructor
@Repository
public class LanguageDAOImpl implements LanguageDAO {
private final JdbcTemplate jdbcTemplate;
private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
@Override
public Long getIdByLangCode(String lang) {
return jdbcTemplate.queryForObject(FIND_ID_BY_LANG_CODE, Long.class, lang);
}
}
構造: 上記のすべてのモデルには生存する権利があります。状況に応じてどちらを使用するかを決定する必要があります。もちろん、それだけではありません。言語ごとに異なるデータベースを使用する、キャッシュ、異なるフレームワークを使用するなど、さらに多くの異なるアプローチがあります。今日はこれで終わりです。
GO TO FULL VERSION