1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lightning AI × TranslateGemmaを用いてdev.toの記事を翻訳してみる

1
Last updated at Posted at 2026-02-09

はじめに

2026年1月15日にGoogleからTranslateGemmaという「Gemma 3」ベースの翻訳特化モデルが発表されました。
4B、12B、27Bの三種類のサイズが用意されており、モバイル端末を含めた多様な環境に対応しています。
また、翻訳の精度を評価するMetricXにおいて12BのTranslateGemmaが27のGemma 3を上回るパフォーマンスを示したことが明らかになっています。

今回はこの翻訳特化モデルのTranslateGemmaをLightning AIで動かし、dev.toのAPIを叩いて記事を翻訳してみました。Lightning AIに関しては正直なところ適当に選んだので特別な選定理由はなく、今考えるとColabの方がやりやすかったと思っています。またpythonは初見なので、変な箇所があると思いますがご了承ください。

前提

二箇所からダウンロードできます

この記事ではHuggingFaceを利用するので先に必要な設定をしておきます。

89a9e315ba6f-20260124.png

  • Gemmaの利用規約に同意

1b4418978b54-20260124.png

  • CLIでログインしておく
hf auth login

main.py

各ファイルの関数を読み込み実行する構成です。

from config import (
    TOP_DAYS,
    PER_PAGE,
    PROCESSED_IDS_PATH,
    HTML_OUTPUT_DIR,
    SOURCE_CODE,
    TARGET_CODE,
    PROMPT,
)
from devto_api import fetch_popular_articles, fetch_article_detail
from dedup import load_processed_ids, save_processed_ids
from translator import translate_markdown
from html_renderer import render_html
from save_html import save_html


def main():
    processed_ids = load_processed_ids(PROCESSED_IDS_PATH)

    articles = fetch_popular_articles(days=TOP_DAYS, per_page=PER_PAGE)

    new_articles = [
        a for a in articles
        if a["id"] not in processed_ids
    ][:PER_PAGE]

    for article in new_articles:
        detail = fetch_article_detail(article["id"])
        body_md = detail.get("body_markdown")
        if not body_md:
            continue

        translated_md = translate_markdown(source_code=SOURCE_CODE, target_code=TARGET_CODE, markdown=body_md, prompt=PROMPT)

        html = render_html(
            title=detail.get("title"),
            body_md=translated_md,
            original_url=detail.get("url"),
        )

        output_path = f"{HTML_OUTPUT_DIR}/{detail.get("id")}.html"
        save_html(output_path, html)

        processed_ids.add(detail.get("id"))

    save_processed_ids(PROCESSED_IDS_PATH, processed_ids)


if __name__ == "__main__":
    main()

それぞれの役割としてはこんな感じです。

  • devto_api.py
    • devtoのAPIで記事を取得する
  • dedup.py
    • 記事翻訳の重複排除
  • translator.py
    • translateGemmaによる翻訳
  • html_renderer.py
    • markdownをhtmlに変換
  • save_html.py
    • htmlファイルを生成

本記事では翻訳部分とdev.toのAPIで記事を取得するところを紹介します

translator.py

translateGemmaによる翻訳部分をします。

from transformers import pipeline
from typing import Any, cast

import torch

MODEL_ID = "google/translategemma-4b-it"
CONTEXT_LIMIT = 8192

pipe = pipeline(
    "image-text-to-text",
    model=MODEL_ID,
    device="cuda",
    dtype=torch.bfloat16
)

def translate_markdown(source_code: str, target_code: str, markdown: str, prompt: str):
    
    full_prompt = f"{prompt} {markdown}"

    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "source_lang_code": source_code,
                    "target_lang_code": target_code,
                    "text": full_prompt,
                }
            ],
        }
    ]

    output = pipe(
        text=cast(Any, messages),
        max_new_tokens=CONTEXT_LIMIT
    )

    return output[0]["generated_text"][-1]["content"]

使用するモデルの設定

transformersのpipelineを使ってモデルの設定をします。

MODEL_ID = "google/translategemma-4b-it"

pipe = pipeline(
    "image-text-to-text",
    model=MODEL_ID,
    device="cuda",
    dtype=torch.bfloat16
)

promptとして渡している文章はこちら。

Constraints:
- Preserve the original Markdown structure exactly.
- Do NOT translate any content inside fenced code blocks (```).
- Do NOT modify URLs in links or images.
- Keep technical terms as close to the original wording as possible.
- Do NOT add, remove, or reorder sections.
- Output must be valid Markdown.

You are a professional {SOURCE_LANG} ({SOURCE_CODE}) to {TARGET_LANG} ({TARGET_CODE}) translator. Your goal is to accurately convey the meaning and nuances of the original {SOURCE_LANG} text while adhering to {TARGET_LANG} grammar, vocabulary, and cultural sensitivities.
Produce only the {TARGET_LANG} translation, without any additional explanations or commentary. Please translate the following {SOURCE_LANG} text into {TARGET_LANG}:


{二行空けてから記事本文}

箇条書きの部分は日本語のプロンプトを雑にGPTで翻訳してもらい、その下の部分は TranslateGemma Technical Report の内容をそのまま利用しています。
SOURCE_LANGTARGET_LANGは言語コード ISO 639-1 に対応しているのでenjaなどが入りますが、モデルにサポートされていないプロパティ(言語)が入るとエラーになります。

スクリーンショット 2026-01-29 14.41.04.png

またTranslateGemmaはGemma 3をベースとしているため、Gemma 3の総出力コンテキストを8192に設定していますが、transformersのAutoTokenizerを使って算出した方がいいかもしれません。

スクリーンショット 2026-01-29 15.04.04.png

devto_api.py

次にdev.toのAPIを叩いて記事を取得する部分です。

import requests

def fetch_popular_articles(days: int, per_page: int = 100):
    url = "https://dev.to/api/articles"
    params = {
        "top": days, # 過去 N 日間人気記事
        "per_page": per_page,
        "page": 1
    }

    response = requests.get(url, params=params, timeout=8)
    response.raise_for_status()
    return response.json()

def fetch_article_detail(article_id: int):
    url = f"https://dev.to/api/articles/{article_id}"

    response = requests.get(url)
    response.raise_for_status()
    return response.json()

topの指定で過去N日間における人気記事を取得できます。
ただ記事複数の場合は記事本文が含まれていないため、fetch_article_detailで個別に記事を取得します。


簡単にはなりますが、コードの説明は以上となります。

所感

体感の話になってしまうのですが、技術的な用語が他の翻訳ツールと比べて違和感なく翻訳されているのを感じました。翻訳自体はローカルで完結するため、機密情報などが外部に漏れないと言うセキュリティ上の観点からも使いやすく、環境によってモデルサイズを選択できるのも良い点です。

最後に

本当はdev.toのAPIで人気記事を取得しTranslateGemmaで翻訳したものをHTML化してデプロイするところまでをcronで組みたかったのですが、Lightning AIの月内の無料枠を使い切ってしまい中途半端に終わってしまいました。また、今回は実装しませんでしたがimage2textの翻訳に関してもかなり精度が高いみたいなので、今後試す機会があれば加筆したいと思います。

内容に誤りや不足がありましたら、コメントでご指摘いただけると幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?