🐍
Pythonクライアントの使い方
Nginx/Apache2配下の自前アプリ(Flask・FastAPI・Djangoなど)に、翻訳クライアントライブラリ「transer」を組み込む手順です。
1
機能概要transerは、HTML全体を渡すと翻訳済みHTML(多言語SEOタグ・言語ボックス自動挿入済み)を受け取れる、薄いPythonクライアントライブラリです。
文結合・抽出・翻訳・再構築・SEOタグ生成はすべてサーバー側(translate.service)が行うため、利用者側は「HTMLを渡して受け取るだけ」のシンプルな実装で済みます。
「翻訳プロキシ」はご自身のサーバーに実装します:transerが提供するのはAPIクライアントと翻訳処理のみです。ブラウザからのリクエストを受けて元ページを取得し、transerで翻訳して返す橋渡し役(下記手順3参照)は、利用者側のアプリとして実装が必要です。
2
ご利用の前提条件(必ずお読みください)このPythonクライアントは、ご自身でサーバーの構築・運用ができるエンジニアの方向けの選択肢です。以下の項目に心当たりが無い場合は、無理に導入を進めず、WordPressプラグイン版のご利用、またはサポートへのご相談をお勧めします。
| 必要な環境・スキル | 説明 |
|---|---|
| サーバーへのアクセス権限 | SSH等で対象サーバーに接続し、コマンドを実行できること |
| ターミナル操作 | Linuxの基本的なコマンド操作に慣れていること |
| プロセス管理の経験 | systemd等で、常駐サービスとして運用した経験があること |
| Python仮想環境 | venv・pipの扱いに慣れていること |
| Python | 3.9 以上 |
| 依存パッケージ | httpx 0.24 以上(pip install時に自動で入ります) |
作業前に決めておくこと:このプロキシアプリを配置する作業フォルダを1つだけ決めてください(例:
~/proxy-app/)。以降の手順は、常にこの同じフォルダに対して行ってください。別のユーザー・別の場所に同名のファイルを複数作らないことが、トラブルを避ける一番のポイントです。古いOSをお使いの場合はご注意ください:例えばUbuntu 16.04は標準でPython 3.5までしか入っておらず、Python 3.9以上を使うには別途インストールが必要です。また、Ubuntu 16.04はサポート自体が既に終了(2021年に標準サポート終了、2024年に延長サポートも終了)しているため、セキュリティの観点からもUbuntu 20.04以降など、サポート中のOSでの運用を推奨します。
3
インストール「APIキー管理」ページから発行したAPIキーを使う前提で、以下の手順で導入します。
# pip でインストール
pip install "https://www.welltranser.com/downloads/transer-0.4.0-py3-none-linux_x86_64.whl"
正式なバージョンタグを切ってからは、
@main部分をそのタグ名(例: @v1.0.0)に固定することを推奨します。1
「APIキー管理」からAPIキーを発行
一度しか表示されないので、発行後すぐに控えてください。
2
「ドメイン管理」でホスト名・契約言語を登録
実際にサイトで使うホスト名(例: example.com)と、翻訳先言語を事前に登録しておく必要があります。未登録のホスト名からのリクエストは403エラーになります。
3
pip installでtranserを追加
上記コマンドで、サーバーのPython環境(venv推奨)にインストールします。
4
基本的な使い方
from transer import Translator
translator = Translator(
base_url="https://api.welltranser.com",
api_key="発行されたAPIキー",
hostname="example.com", # ドメイン管理で登録した値と一致させる
contract_langs=["en", "zh-TW", "ko"], # 契約している翻訳先言語
)
# ページ全体を翻訳する
translated_html = await translator.translate_page(
html=original_html,
pathname="/about",
source_lang="ja",
target_lang="en",
)
hostname・pathnameは必須です:この2つと翻訳先言語の組み合わせで、多言語SEOタグ(canonical・hreflang)のURLが生成されます。
hostnameが未登録の場合はリクエストが403で拒否されます。5
翻訳プロキシの実装例「ブラウザ → あなたのサーバー → transer → 翻訳済みページ」という流れを作る構成例です(FastAPI)。設定ミスにその場で気づけるよう、起動時セルフテスト・稼働確認用エンドポイントを含めています。そのままコピーして使ってください。
# proxy.py
# ※ サポートページ(module-support-python.html)掲載のコード例そのまま。
# API_KEY・HOSTNAME は、実際にダッシュボードで発行/登録した値に
# 書き換えてから起動してください。
import sys
import httpx
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, Response
from transer import Translator
app = FastAPI()
# ▼▼▼ ここを実際にダッシュボードで発行/登録した値に書き換える ▼▼▼
API_KEY = "CHANGE_ME_TO_REAL_API_KEY"
HOSTNAME = "CHANGE_ME_TO_REGISTERED_HOSTNAME"
# ▲▲▲ ここまで ▲▲▲
BASE_URL = "https://api.welltranser.com"
# 書き換え忘れをその場で検知する(気づかずに起動してしまう事故を防ぐ)。
if "CHANGE_ME" in API_KEY or "CHANGE_ME" in HOSTNAME:
print("=" * 60, flush=True)
print("[起動エラー] API_KEY・HOSTNAME がまだ書き換えられていません。", flush=True)
print(" このファイル上部の値を、実際にダッシュボードで発行/登録", flush=True)
print(" した値に書き換えてから、もう一度起動してください。", flush=True)
print("=" * 60, flush=True)
sys.exit(1)
translator = Translator(base_url=BASE_URL, api_key=API_KEY, hostname=HOSTNAME)
ORIGIN_BASE_URL = "http://127.0.0.1:8080" # 既存サイト(このスクリプトが用意したダミーサイト)
# ── SEOオプションについて ──
# 「?hl=en」方式(既定)か「/en/about」仮想パス方式かは、このファイル側では
# 一切設定しない。ダッシュボードの「オプション設定」→「🔍 SEO対応」トグルの
# 状態を、毎リクエスト /contract-langs から取得して従う(環境変数の設定は不要)。
async def fetch_contract_langs_and_seo() -> tuple:
"""
このドメインの、現在の契約言語一覧と「SEO対応」トグルの状態を
毎リクエスト translate.service へ問い合わせて取得する。
ダッシュボードでの変更が、次のリクエストから即座に反映される
(ローカルにハードコード・キャッシュしないため、同期漏れが起きない)。
問い合わせに失敗した場合は、翻訳を行わず原文のまま返す
(通信障害時にサイトが丸ごと止まることを防ぐため)。
"""
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(
f"{BASE_URL}/contract-langs",
params={"hostname": HOSTNAME},
headers={"Authorization": f"Bearer {API_KEY}"},
)
if resp.status_code == 200:
data = resp.json()
return data.get("contract_langs", []), bool(data.get("seo_virtual_path", False))
print(f"[contract-langs] 取得失敗(status={resp.status_code}): {resp.text[:200]}", flush=True)
except Exception as e:
print(f"[contract-langs] 取得失敗(通信エラー): {e}", flush=True)
return [], False
def resolve_lang_and_origin_path(path: str, request: Request, contract_langs: list, seo_virtual_path: bool) -> tuple:
"""
表示する言語と、既存サイト(origin)から取得すべき実際のパスを決定する。
- seo_virtual_path = True の場合(ダッシュボードの「SEO対応」がオン):
URLの先頭セグメントが契約言語と一致するか(例: /en/about)を見る。
一致すればそれを採用し、プレフィックスを除いたパスをoriginへのリクエストに使う。
- seo_virtual_path = False の場合(既定):
?hl=en のようなクエリパラメータを見る(従来通り)。
"""
if seo_virtual_path:
segments = path.split("/", 1)
first_segment = segments[0]
if first_segment in contract_langs:
lang = first_segment
origin_path = segments[1] if len(segments) > 1 else ""
return lang, origin_path
return "ja", path
lang = request.query_params.get("hl", "ja")
return lang, path
def build_langbox_snippet(contract_langs: list, seo_virtual_path: bool) -> str:
"""原文(日本語)ページ用: 言語ボックスのスクリプトタグを組み立てる。"""
langs_attr = ",".join(contract_langs)
# data-seo="1" を付けると、langbox.js側は言語切り替え時にCookie/クエリではなく
# 仮想パス(/en/about等)へ実際にページ遷移するようになる。ダッシュボードの
# 「SEO対応」トグルの値をそのまま反映しているだけなので、お客様側で追加の
# 設定を意識する必要はない。
seo_attr = ' data-seo="1"' if seo_virtual_path else ""
return (
'<script src="https://api.welltranser.com/js/langbox.js?lang=ja" '
f'data-langs="{langs_attr}"{seo_attr} charset="utf-8"></script>'
)
# 起動時、設定内容をログにはっきり出す(「どのファイルが・どの設定で」
# 動いているかを journalctl だけで確認できるようにするため)。
print("=" * 60, flush=True)
print("[起動設定]", flush=True)
print(f" hostname = {HOSTNAME}", flush=True)
print(f" origin_base = {ORIGIN_BASE_URL}", flush=True)
print(" contract_langs・SEO対応の状態は、毎リクエストごとに translate.service へ問い合わせます", flush=True)
print("=" * 60, flush=True)
@app.on_event("startup")
async def startup_selftest():
"""
起動直後、実際に translate.service へ1回だけ疎通確認を行い、
成功/失敗をログにはっきり出す。
api_key・hostname・契約言語の設定ミスは、ここで即座に判明する。
"""
try:
contract_langs, _ = await fetch_contract_langs_and_seo()
if not contract_langs:
print("[起動時セルフテスト] ✗ 失敗: 契約言語一覧を取得できませんでした", flush=True)
print(" → api_key・hostname・「ドメイン管理」での登録状況を確認してください", flush=True)
return
test_html = "<html><head></head><body><p>テスト</p></body></html>"
result = await translator.translate_page(
test_html, pathname="/__selftest", target_lang=contract_langs[0]
)
if "Test" in result or len(result) > len(test_html):
print(
f"[起動時セルフテスト] ✓ 成功: translate.serviceと正常に通信できています"
f"(contract_langs={contract_langs})",
flush=True,
)
else:
print(
f"[起動時セルフテスト] ⚠ 応答はありましたが、翻訳された形跡がありません: {result[:200]}",
flush=True,
)
except Exception as e:
print(f"[起動時セルフテスト] ✗ 失敗: {e}", flush=True)
@app.get("/__health")
async def health():
"""稼働確認用の軽量エンドポイント(curlですぐ確認できるようにするため)"""
contract_langs, seo_virtual_path = await fetch_contract_langs_and_seo()
return {
"status": "ok",
"hostname": HOSTNAME,
"contract_langs": contract_langs,
"origin_base_url": ORIGIN_BASE_URL,
"seo_virtual_path": seo_virtual_path,
}
@app.api_route("/{path:path}", methods=["GET"])
async def proxy(request: Request, path: str):
# 契約言語一覧・SEO対応の状態を、ダッシュボードから毎回取得する
# (SEO対応がオンの場合、URLの先頭セグメントがこの一覧に実在するかどうかの
# 判定にも使う)。
contract_langs, seo_virtual_path = await fetch_contract_langs_and_seo()
lang, origin_path = resolve_lang_and_origin_path(path, request, contract_langs, seo_virtual_path)
async with httpx.AsyncClient() as client:
origin_resp = await client.get(f"{ORIGIN_BASE_URL}/{origin_path}")
content_type = origin_resp.headers.get("content-type", "")
# 【重要】画像・CSS・JS・フォント等の静的ファイルは、翻訳処理を通さず
# そのままバイト列で返す。ここを素通りさせないと、HTMLと同じように
# .text で無理やり文字列化してしまい、バイナリデータが壊れてしまう
# (画像が文字化けして表示される、CSSが読み込めない等の不具合が起きる)。
if "text/html" not in content_type:
return Response(
content=origin_resp.content,
status_code=origin_resp.status_code,
media_type=content_type or None,
)
html = origin_resp.text
# 【重要】lang の値は訪問者側から渡ってくるもの(?hl= または URL)なので、
# 必ず contract_langs に実在するかどうかで判定すること
# ("fr"のような契約外言語をそのまま信用しない)。
if lang in contract_langs:
# pathname は「言語プレフィックスを含まない、ページの識別子」を渡す。
# canonical/hreflangタグはサーバー側がこのpathnameを元に自動生成するため。
html = await translator.translate_page(html, pathname="/"+origin_path, target_lang=lang)
else:
# 原文(日本語)ページ、および契約外の値が指定された場合は、常にこちら。
# target_lang="ja"(原文言語自体)は契約言語一覧に含まれないため、
# translate_page()を呼ぶとサーバー側の契約言語チェックで403エラーになる。
# そのため、原文ページには言語ボックスのスクリプトタグだけを直接挿入する
# (WordPressプラグイン版と同じ考え方)。これが無いと、訪問者が最初に
# 開いた日本語ページには言語を切り替える手段が一切無くなってしまう。
if "</head>" in html:
html = html.replace("</head>", build_langbox_snippet(contract_langs, seo_virtual_path) + "</head>", 1)
# ?hl= やURLによって内容が変わるページのため、ブラウザ・
# 中間プロキシに古い言語のページがキャッシュされないよう、明示的に
# キャッシュを無効化する。
return HTMLResponse(
html,
status_code=origin_resp.status_code,
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
)
SEOオプションを使う場合:ダッシュボードの「オプション設定」で該当ドメインの「🔍 SEO対応(URLに言語コードを付与)」をオンにしてください。これだけで完了です。このアプリ側で何か環境変数を設定する必要は一切ありません(ダッシュボードのトグルの状態を、このアプリが毎リクエスト自動的に問い合わせて従うため)。事前にサーバー側で「言語コード付きURLへの転送設定」を用意しておく必要もありません(このアプリ自体がどんなパスも受け取ってURLを解釈するため)。
契約言語一覧は毎リクエストごとに
/contract-langsへ問い合わせるため、ダッシュボードで言語を追加・削除しても、サーバーの再起動・再デプロイは不要です。日本語ユーザーは翻訳プロキシを経由せず既存サイトへ直接届くため、翻訳機能を追加してもパフォーマンスへの影響はありません。Nginxでの振り分け設定など、より詳しい構成例はサポートページを参照してください。6
段階的な動作確認(必ず順番通りに)途中を飛ばさず、1つずつ確認しながら進めてください。問題があった場合、どの段階で起きたかがすぐに分かります。
1
構文チェック
python3 -m py_compile proxy.py を実行し、何も表示されない(エラーが無い)ことを確認してから次に進んでください。2
サービスを起動し、ログを確認
起動直後のログに
[起動時セルフテスト] ✓ 成功 と出るまで、次には進まないでください。✗ 失敗と出た場合は、表示されたエラー内容(api_key・hostname・契約言語の設定ミスであることがほとんどです)を確認してください。3
稼働確認エンドポイントで確認
curl http://127.0.0.1:8090/__health を実行し、設定したhostname・contract_langsが正しく表示されるか確認してください。4
原文(日本語)ページの確認
パラメータ無しでアクセスし、日本語ページに言語ボックスが表示されるか確認してください。
curl http://127.0.0.1:8090/ | grep langbox.js5
言語切り替えの確認(既定: ?hl=方式)
curl "http://127.0.0.1:8090/?hl=en" を実行し、翻訳された内容が返ってくるか確認してください。ここまで来て初めて、ブラウザでの実機確認に進んでください。6
SEOオプションを使う場合のみ:仮想パスの確認
ダッシュボードの「オプション設定」で「🔍 SEO対応」をオンにした場合、
curl http://127.0.0.1:8090/en/(パラメータ無し)でも英語ページが返ることを確認してください。?hl=enを付けなくても、URLだけで判定されているのがポイントです。ファイルの場所に注意:編集しているファイルと、実際にサービスが読み込んでいるファイルが違う場所にある、という事故が起きやすいポイントです。
systemctl cat お使いのサービス名で、WorkingDirectoryに表示される場所が、実際に編集しているフォルダと完全に一致しているか、必ず確認してください。7
言語切り替えボックスの設置上記の翻訳プロキシはCookieを一切使いません。選択した言語は覚えず、別のページへ移動すれば常に原文言語(日本語)からロードします。原文(日本語)ページには、コード例の通りbuild_langbox_snippet()が組み立てるタグを挿入することで、訪問者が言語を切り替えるためのUIが表示されます。
<script src="https://api.welltranser.com/js/langbox.js?lang=ja" data-langs="en,ko,zh-CN" charset="utf-8"></script>
<!-- ダッシュボードで「SEO対応」がオンのドメインの場合は、さらに data-seo="1" が付く -->
| パラメータ / 属性 | 内容 |
|---|---|
?lang= | 原文言語コード(通常はja) |
data-langs | 契約している翻訳先言語一覧(fetch_contract_langs_and_seo()の結果をそのまま埋め込み)。言語ボックスの選択肢に使われる |
data-seo | ダッシュボードの「SEO対応」がオンのドメインの時だけ付与。langbox.js側がこれを見て、言語切り替え時に?hl=enではなく/en/aboutのような実際のURLへページ遷移するようになる |
build_langbox_snippet()が、原文(日本語)ページの</head>直前にこのタグを自動挿入します。言語ボックス自体は、選ばれた言語に応じて?hl=enを付けてページ遷移する(既定)か、/en/aboutへ遷移する(SEOオプション有効時)かのどちらかの、軽量なUIです。翻訳済みページには、同じ見た目の言語ボックスがtranslate.serviceから自動的に埋め込まれるため、原文・翻訳済みどちらでも同じ操作感になります。8
トラブルシューティング| 症状 | 対処 |
|---|---|
TranslerErrorが発生する | api_keyまたはhostnameが未設定です。Translator(...)の引数を確認してください。 |
| 403エラーになる | 「ドメイン管理」に登録したホスト名と、コード上のhostnameが完全一致しているか確認してください(www の有無等)。 |
| hreflangタグに一部の言語が出ない | 「ドメイン管理」でその言語が契約済み(登録済み)になっているか確認してください。 |
SEOオプションを有効にしたのに/en/が日本語のまま/404になる | ダッシュボードの「オプション設定」で「🔍 SEO対応」がオンになっているか確認してください(curl http://127.0.0.1:8090/__healthのseo_virtual_pathがtrueになっているか)。オンにした直後は、次のリクエストから即座に反映されます。 |
| 通信エラー時、原文がそのまま表示される | これは仕様です。translate_page()は通信失敗時に例外を投げず、元のHTMLをそのまま返します。 |
| コードを書き換えたのに動作が変わらない | 編集しているファイルと、サービスが実際に読み込んでいるファイルが別の場所にある典型的な事故です。systemctl cat お使いのサービス名でWorkingDirectoryを確認し、編集しているフォルダと完全に一致しているか確認してください。 |
| 起動に失敗し続ける(address already in use) | 同じポートを別のプロセスが掴んだままです。sudo lsof -i :ポート番号で確認し、不要なプロセスを終了してください。 |
| 言語を切り替えても反応が無い | まず/__healthで稼働確認、次に起動ログの「起動時セルフテスト」が成功しているか確認してください。ブラウザのキャッシュが原因のこともあるため、強制再読み込み(Ctrl+Shift+R)も試してください。 |
| 画像が文字化けして表示される・CSS/JSが読み込めない | サンプルコードの、Content-Typeがtext/html以外なら素通しする分岐が反映されているか確認してください。これが無いと、画像等の静的ファイルもテキストとして扱われ、バイナリデータが壊れてしまいます。 |