翻訳システム:3技術ハイブリッドアプローチ
この記事では、Flask-Babel(テンプレート用)、TranslationManager(フレーズテーブル用)、DeepSeek AI(コンテンツ翻訳用)の3つの技術を組み合わせて、包括的な多言語サポートを提供する当社の翻訳システムについて説明します。
問題点:複雑なウェブサイトの翻訳
当社のウェブサイトには、翻訳が必要な複数の種類のコンテンツがあります:
-
静的テンプレート: ナビゲーション、ボタン、ラベル(500文字以上)
-
動的コンテンツ: 製品名、説明、機能(10,000文字以上)
-
ユーザー生成コンテンツ: レビュー、コメント、サポートチケット
-
専門用語: ブランド名、SKU、URL(翻訳してはならない)
単一の翻訳アプローチでは機能しません:
-
機械翻訳: 高速だが専門用語では不正確
-
手動翻訳: 正確だが遅く、コストがかかる
-
テンプレートのみ: 動的コンテンツを処理できない
これら3つの長所を組み合わせたハイブリッドアプローチが必要です。
翻訳アーキテクチャ
graph TD
Request[ユーザーリクエスト
lang=hi]
subgraph Templates
Babel[Flask-Babel
.poファイル]
Static[静的文字列
ボタン、ラベル]
end
subgraph Dynamic
TM[TranslationManager
フレーズテーブル]
Phrases[製品名
機能、用語]
end
subgraph AI
DS[DeepSeek API
AI翻訳]
Content[説明文
記事、長文]
end
Request --> Babel
Request --> TM
Request --> DS
Babel --> Static
TM --> Phrases
DS --> Content
Static --> Response[翻訳済みページ]
Phrases --> Response
Content --> Response3つの技術
1. Flask-Babel: テンプレート翻訳
Flask-Babelは、gettext .poファイルを使用して静的テンプレート文字列を処理します。
-
使用例: ナビゲーション、ボタン、ラベル、エラーメッセージ
-
例:
# テンプレート
{{ _('Add to Cart') }}
# .poファイル(ヒンディー語)
msgid "Add to Cart"
msgstr "कार्ट में जोड़ें"
利点:
-
✅ 標準的なi18nアプローチ
-
✅ 翻訳ツールのサポート(Poedit、Weblate)
-
✅ コンパイル時検証
-
✅ 高速ルックアップ(コンパイル済み.moファイル)
制限:
-
❌ 静的文字列のみに有効
-
❌ 新しい文字列を追加するにはコード変更が必要
-
❌ 動的コンテンツのサポートなし
2. TranslationManager: フレーズテーブル
カスタムTranslationManagerクラスは、JSONフレーズテーブルを使用して動的コンテンツを処理します。
-
使用例: 製品名、機能、仕様、説明
-
例:
{
"Mini PC": "मिनी पीसी",
"Intel N100": "Intel N100",
"8GB RAM": "8GB RAM",
"256GB SSD": "256GB SSD"
}
利点:
-
✅ 動的コンテンツのサポート
-
✅ ランタイム更新(再起動不要)
-
✅ 保持ルール(ブランド名、SKU)
-
✅ 地域別フォーマット(小数点区切り)
-
✅ 翻訳キュー(欠落文字列)
制限:
-
❌ 手動フレーズ管理
-
❌ 翻訳者向けのコンテキストなし
-
❌ 初期翻訳が必要
3. DeepSeek AI: コンテンツ翻訳
DeepSeek AIモデルは、長文コンテンツの翻訳を処理します。
-
使用例: ブログ記事、製品説明、マーケティングコピー
-
例:
prompt = f"Translate to {lang}: {text}"
response = deepseek.generate(prompt)
利点:
-
✅ 長文コンテンツの処理(1000語以上)
-
✅ コンテキストを考慮した翻訳
-
✅ 自然な言語出力
-
✅ バッチ処理
制限:
-
❌ 翻訳ごとのAPIコスト
-
❌ インターネット接続が必要
-
❌ 専門用語を誤って翻訳する可能性あり
TranslationManagerアーキテクチャ
フレーズテーブル構造
フレーズテーブルは言語ごとにJSONファイルとして保存されます:
app/shared/translation/phrase_tables/
各ファイルは英語のフレーズと翻訳をマッピングします:
{
"Mini PC": "मिनी पीसी",
"Thin Client": "थिन क्लाइंट",
"Industrial PC": "औद्योगिक पीसी",
"All-in-One": "ऑल-इन-वन"
}
翻訳ルックアップ
get()メソッドは多段階ルックアップを実行します:
1. 保持チェック:
if self._should_skip_translation(text, lang):
return text # ブランド名、SKU、URLは翻訳しない
2. カンマ分割(長い文字列用):
if len(text) > 150 and "," in text:
parts = text.split(",")
return ", ".join(self.get(p, lang) for p in parts)
3. フレーズテーブルルックアップ:
if text in self.phrase_tables[lang]:
return self.phrase_tables[lang][text]
4. レガシーキャッシュ昇格:
if text_hash in self.legacy_caches[lang]:
translation = self.legacy_caches[lang][text_hash]
self.set(text, lang, translation) # フレーズテーブルに昇格
return translation
5. 欠落時はキュー追加:
if queue_if_missing:
self.add_to_queue(text, lang)
return None # 翻訳が見つからない
保持ルール
特定のコンテンツタイプを保持します:
ブランド名:
BRAND_NAMES_PRESERVE = [
"Thinvent", "Intel", "AMD", "Microsoft", "Ubuntu",
"Windows", "Linux", "WiFi", "Bluetooth"
]
専門用語:
TECHNICAL_TERMS_PRESERVE = [
"HDMI", "DisplayPort", "VGA", "USB", "Ethernet",
"DDR4", "SSD", "NVMe", "PCIe", "SATA"
]
SKU(パターンベース):
if not " " in text and text.count("-") >= 2:
return True # "Treo-N100-8-256-2H-W6-11P"を保持
URL:
if text.startswith(("/", "http://", "https://")):
return True
地域別フォーマット
欧州ロケール(ドイツ語、フランス語、スペイン語)では、地域別フォーマットを適用します:
小数点区切り:
# 英語: 34.00
# ドイツ語: 34,00
text = text.replace(".", ",")
電圧/電流:
# 英語: 12.5V
# ドイツ語: 12,5V
text = re.sub(r"(\d+)\.(\d+)V", r"\1,\2V", text)
これにより、各ロケールで数字が正しく表示されます。
翻訳キュー
欠落した翻訳はバッチ処理のためにキューに追加されます:
[
{
"text": "Compact Desktop",
"lang": "hi",
"hash": "a1b2c3d4e5f6"
},
{
"text": "Fanless Design",
"lang": "hi",
"hash": "f6e5d4c3b2a1"
}
]
バックグラウンドスクリプトがDeepSeek AIを使用してキューを処理します:
for item in queue:
translation = deepseek.translate(item["text"], item["lang"])
translation_manager.set(item["text"], item["lang"], translation)
ファイルロック
fcntlファイルロックを使用して、同時書き込みを防止します:
with open(lock_path, "w") as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
# フレーズテーブルの読み取り、変更、書き込み
with open(table_path, "r+") as f:
data = json.load(f)
data.update(new_translations)
f.seek(0)
json.dump(data, f)
f.truncate()
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
これにより、複数のプロセスがフレーズテーブルを破損しないようにします。
言語検出
Unicode範囲とストップワードを使用してクエリ言語を検出します:
Unicode範囲検出
デーヴァナーガリー文字(ヒンディー語、マラーティー語):
if 0x0900 <= ord(char) <= 0x097F:
return "hi"
ベンガル文字:
if 0x0980 <= ord(char) <= 0x09FF:
return "bn"
アラビア文字:
if 0x0600 <= ord(char) <= 0x06FF:
return "ar"
キリル文字(ロシア語):
if 0x0400 <= ord(char) <= 0x04FF:
return "ru"
CJK(中国語、日本語、韓国語):
if 0x4E00 <= ord(char) <= 0x9FFF: # 漢字
has_cjk = True
if 0x3040 <= ord(char) <= 0x30FF: # ひらがな/カタカナ
return "ja"
if 0xAC00 <= ord(char) <= 0xD7AF: # ハングル
return "ko"
ストップワード検出
ラテン系言語では、ストップワードを使用します:
スペイン語:
SPANISH_STOPWORDS = {
"el", "la", "los", "las", "un", "una", "para", "con",
"en", "por", "que", "es", "su", "y", "del", "al"
}
フランス語:
FRENCH_STOPWORDS = {
"le", "la", "les", "des", "du", "un", "une", "pour",
"avec", "en", "est", "sur", "et", "au", "dans"
}
ドイツ語:
GERMAN_STOPWORDS = {
"der", "die", "das", "ein", "eine", "für", "mit",
"ist", "und", "auf", "den", "dem", "bei", "von"
}
いずれかのストップワードが一致すると、その言語を返します。
動的言語検出
ユーザーは以下の方法で言語を指定できます:
1. URLパラメータ
/p/Treo-N100-8-256-2H-W6-11P?lang=hi
2. クッキー
response.set_cookie("lang", "hi", max_age=31536000)
3. ブラウザのAccept-Languageヘッダー
lang = request.accept_languages.best_match(["en", "hi", "es", "fr", "de"])
優先順位: URLパラメータ > クッキー > Accept-Language > デフォルト(en)
再帰的翻訳
ネストされたデータ構造を再帰的に翻訳します:
def translate_data(obj, lang):
if isinstance(obj, dict):
return {k: translate_data(v, lang) for k, v in obj.items()}
elif isinstance(obj, list):
return [translate_data(item, lang) for item in obj]
elif isinstance(obj, str):
return self.get(obj, lang) or obj
return obj
例:
data = {
"title": "Mini PC",
"features": {
"RAM": "8GB",
"Storage": "256GB SSD"
},
"price": 25000
}
translated = translate_data(data, "hi")
# 結果:
# {
# "title": "मिनी पीसी",
# "features": {
# "RAM": "8GB",
# "Storage": "256GB SSD"
# },
# "price": 25000
# }
機能翻訳
製品機能は特別な処理が必要です:
def translate_features(features, lang):
translated = {}
for heading, feature_dict in features.items():
# 見出しを翻訳
translated_heading = self.get(heading, lang) or heading
# 機能名と値を翻訳
translated_features = {}
for name, value in feature_dict.items():
translated_name = self.get(name, lang) or name
translated_value = self.get(value, lang) or value
translated_features[translated_name] = translated_value
translated[translated_heading] = translated_features
return translated
例:
features = {
"Processing": {
"Processor": "Intel N100",
"Cores": "4",
"RAM": "8GB"
}
}
translated = translate_features(features, "hi")
# 結果:
# {
# "प्रोसेसिंग": {
# "प्रोसेसर": "Intel N100",
# "कोर": "4",
# "RAM": "8GB"
# }
# }
SEOパイプラインとの統合
翻訳システムはSEOパイプラインと統合されています:
クエリ言語の伝播
ユーザーがヒンディー語で検索すると、言語を関連検索に伝播します:
if SEO_ENABLE_LANGUAGE_PROPAGATION:
lang = detect_query_language(query)
if lang != "en":
url = f"{url}?lang={lang}"
これにより、ユーザーが関連検索をクリックしたときに、好みの言語に留まることが保証されます。
翻訳済みクエリページ
クエリページは複数の言語で生成されます:
/q/mini-pc (英語)
/q/mini-pc?lang=hi (ヒンディー語)
/q/mini-pc?lang=es (スペイン語)
コンテンツはTranslationManagerを使用して翻訳されます。
パフォーマンス特性
フレーズテーブルルックアップ:
-
時間: O(1)辞書ルックアップ(〜1μs)
-
メモリ: 言語ごとに〜5 MB(10,000フレーズ)
言語検出:
-
時間: O(n)、n = 文字列長(100文字で〜10μs)
-
メモリ: 無視できる程度
再帰的翻訳:
-
時間: O(n)、n = 文字列数(100文字列で〜1ms)
-
メモリ: データ構造のサイズに比例
ファイルロック:
-
時間: ロック取得ごとに〜1ms
-
競合: 稀(書き込みは頻繁ではない)
参考文献
技術的概念
-
国際化(i18n) - Wikipedia
-
Unicodeブロック - Wikipedia
-
Gettext - GNUドキュメント
-
[fcntl](https://docs.python.org