翻訳システム: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 --> Response

3つの技術

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

  • 競合: 稀(書き込みは頻繁ではない)

参考文献

技術的概念