# General Translation Knowledge Base: SRT 字幕のタイミングを崩さずに翻訳するには？
URL: https://generaltranslation.com/ja/kb/translate-srt-subtitles.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: SRT ファイルではキューのテキストだけを翻訳し、キュー番号、タイムコード、区切りの空行、書式タグは翻訳元のまま残します。そのうえで、1 行あたりの文字数と読み取り速度が配信先の要件を満たしているかを確認します。たとえば Netflix の英語ガイドでは、成人向け番組の上限を 1 行 42 文字、1 秒あたり 20 文字としています。
Category: 翻訳
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

SRT (SubRip) には正式な仕様がありません。米国議会図書館もこの形式を[「十分に標準化されていない」](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml)としており、プレーヤーやプラットフォームによって受け付ける内容が異なります。そのため、翻訳済みファイルは翻訳元と同じくシンプルな構成に保つのが最も安全です。

## Video

The video is narrated in English, with English captions.

[How do I translate SRT subtitles without breaking timing?](https://caxecubb9c64cezv.public.blob.vercel-storage.com/films/kb-translate-srt-subtitles.mp4) (45 秒)

Translate only the cue text in an SRT file; keep cue numbers, timecodes, blank-line separators, and formatting tags as in the source. Then check line length and reading speed against your destination's requirements, such as Netflix's 42 characters per line. With General Translation, add an srt entry under files in `gt.config.json` and run `npx gt translate`.

## SRT ファイルの構成

SRT ファイルは、空行で区切られたキューの並びです。各キューは、連番、`HH:MM:SS,mmm --> HH:MM:SS,mmm` 形式のタイミング行 (ミリ秒の前はカンマで区切る) 、1 行以上のテキストで構成されます。

```text title="subtitles/en/intro.srt"
1
00:00:01,000 --> 00:00:03,500
Welcome back to the channel.

2
00:00:03,600 --> 00:00:06,200
Today we're looking at
<i>how subtitles are timed</i>.

3
00:00:06,400 --> 00:00:08,000
Let's get started.
```

このガイドでは、タイミングを固定したワークフローを採用します。つまり、既存のキュー構造はそのままにテキストだけを翻訳します。対象言語に合わせてタイミングやキューの区切りを変える必要がある場合は、字幕エディターで動画を見ながら調整し、編集後の結果を別途検証してください。

* **キュー番号**：キューごとに1つずつ、順番どおりに保持します。
* **タイミング行**：1文字も変えずに保持します。カンマがドットに置き換わっていたり、ミリ秒が1つずれていたりするだけで、プレーヤーがキューを受け付けなかったり、誤ったタイミングで表示したりすることがあります。
* **キュー間の空行。** 区切りが欠けていると、プレーヤーが該当するキューを結合したり、誤って解釈したり、読み飛ばしたりすることがあります。後続のキューは絶対タイムスタンプで指定されているため、タイミングが自動的に調整されることはありません。
* **キュー内の改行**：各行を読みやすい長さに収めるためのものです。翻訳に合わせて改行位置を調整するのは構いませんが、3行目を追加しないでください。
* `<i>`、`<b>`、`<font>` などの**タグ**、および `{\an8}` などの位置タグ：プレーヤーはこれらを書式指定として解釈します。実際にレンダリングされるかどうかはプレーヤー次第です。たとえば YouTube は基本的な SRT ファイルのみに対応しており、マークアップは一切認識せず、プレーンな UTF-8 が必須です ([サポートされている字幕ファイル](https://support.google.com/youtube/answer/2734698)) 。
* **テキストエンコーディング。** SRT にはエンコーディングの標準規格がないため、プレーヤーが別の形式を必要としない限り、翻訳は UTF-8 で保存してください。

## 1行の文字数と読み取り速度

翻訳でテキストの長さが変わっても表示タイミングは変わらないため、英語では無理なく読めた字幕が、別の言語では一瞬で消えてしまうことがあります。Netflix はこの制限を言語ごとに公開しています。[English (USA) Timed Text Style Guide](https://partnerhelp.netflixstudios.com/hc/en-us/articles/217350977-English-USA-Timed-Text-Style-Guide) では、1行あたり42文字、最大2行、読み取り速度は成人向け番組で毎秒20文字まで (子ども向け番組では17文字) と定められています。[ドイツ語版ガイド](https://partnerhelp.netflixstudios.com/hc/en-us/articles/217351587-German-Timed-Text-Style-Guide) では、1行あたり42文字は同じですが、読み取り速度は成人向けで毎秒17文字、子ども向けで13文字に引き下げられています。

表示できる時間は変わらないのに、翻訳文のほうが翻訳元より多くの文字数を必要とすることがあります。毎秒の文字数は、翻訳後の各キューの文字数と表示時間から算出してください。レンダリング後のテキストの幅とは別の指標です。キューが納品仕様を超える場合は、意味を損なわないよう言い換えたうえで、実際の動画上で確認してください。

## キューごとに翻訳するか、ファイル全体を文脈として翻訳するか

台詞は2〜3個のキューにまたがることがよくあります。キューを1つずつ翻訳すれば構造が崩れる心配はありませんが、断片的な文を翻訳することになります。たとえばドイツ語では従属節の動詞が文末に置かれるため、その動詞が次のキューにずれ込むことがあります。前後のキューを文脈として含めて翻訳したほうが自然な訳文になります。ただし、訳文を元と同じキューに、同じタイミングで戻すことが前提です。

## SRT ファイルを翻訳する方法

| 方法                          | SRT 構造の扱い                                  | 機械翻訳                                   | 読み取り速度のチェック                            | 適している場面                               |
| --------------------------- | ------------------------------------------ | -------------------------------------- | -------------------------------------- | ------------------------------------- |
| Subtitle Edit (無料、オープンソース)  | SRT を解析して書き出す                              | Google Translate、DeepL、LLM API、ローカルモデル | 1 秒あたりの文字数、行の長さ、行数に関する Netflix 方式のチェック | 1 人が手作業でファイルを翻訳・推敲する場合                |
| DeepL のドキュメント翻訳             | `.srt` のアップロードに対応                          | DeepL                                  | なし                                     | 1 つのファイルの初稿を手早く作りたい場合                 |
| Google Translate            | 一般向けアプリ、Cloud のドキュメント翻訳ともに SRT のアップロードに非対応 | テキスト API のみ。ファイルは自分で組み立て直す             | なし                                     | スクリプトを書き、ファイル処理を自前で行う場合               |
| General Translation CLI     | 専用の SRT 処理により、キューのタイミングと保護されたマークアップを保持     | 用語集とカスタムプロンプトを共有する GT のサービス            | 以下のチェックを実行し、動画と照らし合わせて確認する             | 製品やドキュメントの定期的なリリースに合わせて字幕をバージョン管理する場合 |
| LLM にファイルを渡して指示する           | ファイルをプレーンテキストとして読み込む。構造が保たれる保証はない          | 任意のモデル                                 | 指示した場合のみ。検証はされない                       | 1 行ずつ確認する単発のファイル                      |

表について、いくつか補足します。YouTube Studio を表に含めていないのは、字幕を翻訳するツールではなく、公開するためのツールだからです。翻訳済みファイルが用意できたら、**字幕**を開いて動画を選択し、**言語を追加**をクリックして言語を選び、ファイルをアップロードします ([字幕を追加する](https://support.google.com/youtube/answer/2734796)) 。つまり、アップロードの時点で翻訳はすでに完成している必要があります。[Subtitle Edit](https://github.com/SubtitleEdit/subtitleedit) (MIT ライセンス、Windows・macOS・Linux 対応) には **Translate &gt; Auto-translate** ダイアログがあり、Google Translate、DeepL、ChatGPT、Claude、Gemini のほか、Ollama 経由のローカルモデルなどのエンジンを利用できます。さらに、**Tools &gt; Check and fix Netflix errors** というチェッカーも備えています。DeepL の[ドキュメント API](https://developers.deepl.com/api-reference/document) は、対応形式として `srt` を挙げています。Google の[ドキュメント翻訳](https://docs.cloud.google.com/translate/docs/advanced/translate-documents)が対応しているのは Office ファイルと PDF で、[一般向けサイト](https://support.google.com/translate/answer/2534559?co=GENIE.Platform%3DDesktop)は `.docx`、`.pdf`、`.pptx`、`.xlsx` にしか対応していません。

1 人が手作業でファイルを翻訳・推敲するなら、機械翻訳とタイミングや読み取り速度のチェックを併用できる Subtitle Edit がおすすめです。1 つのファイルの初稿を手早く作りたい場合は、DeepL のドキュメント翻訳が向いています。同じ字幕ファイルをリリースのたびに更新し、アプリやドキュメントと用語を統一する必要がある場合は General Translation を選びましょう。その場合も、動画と照らし合わせた最終確認には字幕エディタを使ってください。

## 翻訳済みファイルを確認する

この Node.js スクリプトは、翻訳済みファイルのキュー数、キュー番号、タイムコードが翻訳元と一致しているか、また空のキューがないかをチェックします。3 番目の引数を渡すと、指定した読み取り速度 (たとえばドイツ語なら 1 秒あたり 17 文字) を超えるキューを検出できます。

```js title="check-srt.mjs"
// 使い方: node check-srt.mjs <source.srt> <translated.srt> [1秒あたりの最大文字数]
import { readFileSync } from 'node:fs';

const TIMING = /^(\d{2}:[0-5]\d:[0-5]\d,\d{3}) --> (\d{2}:[0-5]\d:[0-5]\d,\d{3})$/;
const cues = (file) =>
  readFileSync(file, 'utf8')
    .replace(/^﻿/, '')
    .split(/\r?\n\s*\r?\n/)
    .map((block) => block.trim().split(/\r?\n/))
    .filter((lines) => lines[0])
    .map(([number, timing = '', ...text]) => ({ number, timing, text }));

const [srcFile, outFile, maxCps] = process.argv.slice(2);
if (!srcFile || !outFile || (maxCps !== undefined && !(Number(maxCps) > 0 && Number.isFinite(Number(maxCps))))) {
  throw new Error('Provide source and translated files, and an optional positive reading-speed limit.');
}
const [source, target] = [srcFile, outFile].map(cues);
const seconds = (tc) => tc.split(/[:,]/).reduce((acc, n, i) => acc + n * [3600, 60, 1, 0.001][i], 0);
const problems = [];
if (!source.length) problems.push('source has no cues');
if (source.length !== target.length) problems.push(`cue count: ${source.length} in source, ${target.length} translated`);

source.forEach((cue, i) => {
  const t = target[i];
  if (!t) return;
  if (cue.number !== t.number) problems.push(`cue ${i + 1}: number "${cue.number}" became "${t.number}"`);
  if (!TIMING.test(t.timing)) problems.push(`cue ${cue.number}: invalid timing line "${t.timing}"`);
  else if (cue.timing !== t.timing) problems.push(`cue ${cue.number}: timing changed to "${t.timing}"`);
  if (!t.text.join('').trim()) problems.push(`cue ${cue.number}: empty text`);
  const [, start, end] = TIMING.exec(t.timing) ?? [];
  const duration = start ? seconds(end) - seconds(start) : 0;
  if (start && duration <= 0) problems.push(`cue ${cue.number}: end must be after start`);
  if (maxCps && duration > 0) {
    // スペースと句読点は数えるが、<i> や {\an8} などのマークアップは数えない
    const chars = t.text.join(' ').replace(/<[^>]+>|\{\\an\d\}/g, '').length;
    const cps = chars / duration;
    if (cps > maxCps) problems.push(`cue ${cue.number}: ${cps.toFixed(1)} characters per second`);
  }
});

console.log(problems.length ? problems.join('\n') : `OK: ${source.length} cues, numbers and timecodes match`);
process.exitCode = problems.length ? 1 : 0;
```

以下は、サンプルファイルを2通りに翻訳した例です。スペイン語版ではすべてのキューがそのまま残っています。一方、ドイツ語版では3つのキューが2つにまとめられ、さらにタイムコードにドットが入力されています。

```text title="subtitles/es/intro.srt"
1
00:00:01,000 --> 00:00:03,500
Bienvenidos de nuevo al canal.

2
00:00:03,600 --> 00:00:06,200
Hoy veremos
<i>cómo se sincronizan los subtítulos</i>.

3
00:00:06,400 --> 00:00:08,000
¡Empecemos!
```

```text title="subtitles/de/intro.srt"
1
00:00:01,000 --> 00:00:03,500
Willkommen zurück auf dem Kanal.
Heute sehen wir uns an,

2
00:00:03,600 --> 00:00:06.200
<i>wie Untertitel getimt werden</i>.
Los geht's!
```

Node.js 24 でスクリプトを実行すると、スペイン語のファイルはチェックを通過しますが、ドイツ語のファイルは失敗します。キュー 1 は 2.5 秒間に 56 文字になっています。

```bash
$ node check-srt.mjs subtitles/en/intro.srt subtitles/es/intro.srt 20
OK: 3 cues, numbers and timecodes match

$ node check-srt.mjs subtitles/en/intro.srt subtitles/de/intro.srt 17
cue count: 3 in source, 2 translated
cue 1: 22.4 characters per second
cue 2: invalid timing line "00:00:03,600 --> 00:00:06.200"
```

このスクリプトは問題を検出すると終了コード 1 で終了するため、翻訳後に CI で実行できます。対応しているのはここで示したシンプルなタイミング行のみで、任意の位置座標には対応していません。チェック対象は、キューの対応関係、タイミングの妥当性、テキストが空でないこと、および任意で設定できる表示速度の上限です。タグの保持、行の長さ、行数、意味はチェックしません。配信先の字幕チェッカーを使用したうえで、その言語を話すレビュー担当者と一緒に結果を視聴して確認してください。JavaScript による文字数カウントはあくまで簡易チェックであり、各プラットフォーム独自のカウントルールの代わりにはなりません。

## General Translation で SRT ファイルを翻訳する方法

リポジトリで管理している字幕ファイルを、変更のたびに複数の言語へ翻訳する必要がある場合は、General Translation の CLI である `gt` を使えます。`gt` はバージョン 2.22.2 以降で `.srt` ファイルの翻訳に対応しています。インストールとサインインが済んだら ([CLI クイックスタート](/docs/cli/quickstart)を参照) 、次のように `gt.config.json` の `files` に `srt` エントリを追加し、パスに `[locale]` プレースホルダーを含めます。

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "de"],
  "files": {
    "srt": {
      "include": ["subtitles/[locale]/**/*.srt"]
    }
  }
}
```

次に [`npx gt translate`](/docs/cli/reference/commands/translate) を実行します。CLI は `subtitles/en/` から翻訳元の字幕を読み込み、翻訳結果を `subtitles/es/` と `subtitles/de/` に書き出します。英語に翻訳する場合は、`defaultLocale` に翻訳元言語を設定し、`locales` に `"en"` を追加してください。

翻訳されるのはキューのテキストのみです。キュー番号、タイミング行 (タイムスタンプの後に続く位置データを含む) 、空行による区切り、`<i>` や `<font>` などのマークアップ、先頭の `{\an1}` ～ `{\an9}` 位置タグは変更されません。有効なタイミング行を持たないブロックもそのまま残ります。翻訳された行は元のキューの改行コードを引き継ぎ、ダウンロード時には翻訳元ファイルのエンコーディングが使用されます。変更のないキューには以前の翻訳を再利用でき、プロジェクトの用語集とカスタムプロンプトも字幕に適用されます ([SRT 形式リファレンス](/docs/cli/reference/formats/srt-files)および[翻訳の文脈を定義する](/docs/platform/dashboard/guides/defining-context-for-translations)を参照) 。

タイミング行は翻訳元から一切変更されないため、翻訳済みファイルごとに上記のチェックスクリプトを実行してください。翻訳元よりテキストが長くなりがちな言語では、特に確認が重要です。

## 要点

* タイミングを固定した翻訳では、キュー番号、タイミング行、区切り文字、書式タグはそのままにして、キューのテキストだけを変更します。タイミングやキューの境界を意図的に変更した場合は、映像と照らし合わせて別途確認してください。
* Netflix の基準では 1 行あたり 42 文字、最大 2 行までとされており、成人向け番組の読み取り速度の上限は英語で毎秒 20 文字、ドイツ語で毎秒 17 文字です。
* DeepL のドキュメント翻訳と Subtitle Edit は SRT ファイルに対応していますが、Google Translate のドキュメントアップロードは対応していません。
* `gt` CLI (2.22.2 以降) では、`[locale]` を含むパスを `files.srt.include` に追加して [`npx gt translate`](/docs/cli/reference/commands/translate) を実行すると、SRT を翻訳できます。翻訳されるのはキューのテキストのみで、タイミングは翻訳元のまま維持されます。

## Related questions

- [Markdown と MDX の構文を壊さずに翻訳するには？](https://generaltranslation.com/ja/kb/translate-markdown-files.mdx): 実行可能なコード、インポート文、JSX コンポーネント名、機械が参照する識別子はそのまま残し、読者向けのテキストだけを翻訳します。General Translation は Markdown と MDX を直接処理でき、表示テキストを持つ props やフロントマターのタイトルも翻訳対象に含めます。ドキュメントが更新されても、一致する既存の翻訳を再利用します。公開前には、ローカライズされたファイルをレビューし、サイトのビルドを実行してください。
- [i18n の JSON ファイルを翻訳するには？](https://generaltranslation.com/ja/kb/translate-json-files.mdx): JSON ロケールファイルでは、文字列の値だけを翻訳します。複数形以外のキー、{{name}} などのプレースホルダー、タグは変更せずに残しますが、複数形のキーは言語によって異なる点に注意が必要です。たとえばポーランド語では inbox_few と inbox_many が必要になります。ファイル形式に対応したツールや API を使い、レビューの前に出力をパースして検証してください。
- [AI翻訳は本番環境のアプリで使えるほど正確か？](https://generaltranslation.com/ja/kb/is-ai-translation-accurate.mdx): 広く話されている言語であれば多くの場合は十分な精度がありますが、それは自社の対象言語、UI文字列、リスクの高いコンテンツで評価をクリアしてからの話です。WMT25では、Gemini 2.5 Proが16の言語ペアのうち10で人間による参照訳と同等以上の評価を得ました。しかし、文書単位のベンチマークからは、短いインターフェース文字列をそのままリリースできるかどうかまではわかりません。
- [Google 翻訳の代替：General Translation と Google 翻訳の比較](https://generaltranslation.com/ja/kb/general-translation-vs-google-translate.mdx): Google 翻訳は、単発のテキストをすばやく翻訳するのに便利ですが、製品を別の言語でリリースするには、各文字列を文脈に沿って翻訳し、用語を統一し、変数や複数形を正しく扱い、レビューを行い、リリースプロセスを整える必要があります。General Translation はコード内の各文字列の文脈を理解し、コードからレビュー、そして毎回のリリースまで、ローカライゼーションのワークフロー全体を自動化します。

## Previous and next questions

- Previous question: [Markdown と MDX の構文を壊さずに翻訳するには？](https://generaltranslation.com/ja/kb/translate-markdown-files.mdx)
- Next question: [String Catalog を使って iOS アプリをローカライズするには？](https://generaltranslation.com/ja/kb/localize-ios-app.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ja/kb/sitemap.md) for every article.
