Amazon Web Services ブログ

CI/CD パイプラインで Amazon Translate を使用してウェブサイトまたはアプリケーションを自動的に翻訳する

AWS では、ウェブサイトやアプリケーションを数分でグローバルにデプロイできます。これは、大企業であっても個人の開発者であっても、世界中のユーザー、つまり潜在的な顧客にリーチできることを意味します。けれども、最高のエクスペリエンスを提供するには、顧客に身近なコンテンツを提供するだけでなく、そのコンテンツを顧客の母国語で利用できるようにする必要があります。

ウェブサイトやアプリケーションの翻訳は、ローカリゼーションおよびインターナショナライゼーション (それぞれ L10N および I18N) と呼ばれるプロセスの一部です。コンテンツをローカライズするために、企業は翻訳者を雇う (専門的なリソースが必要で、ターゲット言語の数が多い場合はさらにその傾向が顕著) か、システムを構築したのと同じ開発者にタスクを割り当てます (これは最適な結果が得られることを保障できず、また開発者がより重要なタスクを遂行する上で支障になる)。

Amazon Translate は、手頃な価格で迅速かつ高品質な翻訳を実現するニューラル機械翻訳サービスです。サポートされている言語の詳細については、「Amazon Translate とは?」 を参照してください。 Amazon Translate は、自動的にスケーリングして大量のテキストを処理します。ビジネスの特定の詳細情報を翻訳するようにカスタマイズでき、料金は翻訳したテキストの量に対してのみ発生します。

この記事は、英語で記述された UI を含むウェブサイトと、Amazon Translate を使用して自動的にスペイン語にローカライズする継続的統合パイプラインを作成します。次の図は、このソリューションのアーキテクチャを示しています。

 

ウェブサイトのローカリゼーションに関する入門書

ウェブサイトまたはアプリケーションのローカライズは、通常、開発者と翻訳者の間で共有するタスクです。開発者は、ローカライズする必要のあるテキストを含むユーザーインターフェイスへのプレースホルダーまたはタグの挿入を監視する一方、翻訳者は、このようなプレースホルダーを必要な言語に翻訳する責任を負っています。

各チームの責任をより適切に分離し、メンテナンスを容易にするために、開発者は通常、翻訳ペアのみを含む翻訳者用のファイルを個別に作成しています。翻訳の発生方法とこれらのファイルの形式に関する具体的な詳細は、ローカライズするコンポーネントの言語、フレームワーク、テクノロジースタックによって異なりますが、全体的な考え方は通常同じです。(たとえば、Symfony を使用する PHP サイトは YAML ファイルに依存し、Spring で記述した Java アプリはおそらく.properties ファイルを使用しています)。

この記事は Flask で記述された単純な Python ウェブサイトで動作し、ウェブブラウザから送信される Accept-Language ヘッダー (値はユーザーの設定によって異なります) に応じて言語を切り替えます。ウェブサイトは実際に出来上がった翻訳を処理するために Babel と呼ばれるパッケージを使用しています。Babel は、Python の gettext モジュールの上にあるユーティリティとラッパーのセットで、同時に GNU gettext 上の抽象化レイヤーでもあります。

Babel では、ユーザーインターフェイスまたはウェブページをデザインし、テキストの一部を翻訳する場合は、翻訳エンジンにそれを置き換えるように指示する必要があります。次の HTML 段落を例に取ります。

<p>Hello</p>

上記を翻訳するには、次のように gettext() 関数でテキスト文字列を包むだけです:

<p>{{ gettext(‘Hello’) }}</p>

次の短縮エイリアス _() を使用することもでき、同じ結果が得られます。

<p>{{ _(‘Hello’) }}</p>

gettext() 関数は、現在アクティブな言語の文字列「Hello」に対応する翻訳を検索し、その値を返します。翻訳が定義されていない場合は、元のテキストが英語で返されます。(中括弧は、Jinja が使用するレンダリングタグで、Flask にバンドルされているテンプレートエンジンです)。

この時点までに、翻訳対象を指定しましたが、実際の翻訳も提供する必要があります。gettext では、翻訳は、それぞれ .po および .mo 拡張子を持つポータブルオブジェクト (PO) およびマシンオブジェクト (MO) ファイルに保持されます。PO ファイルは次のテキストスニペットのようになります。

#: file.html:23
msgid “Hello”
msgstr “Hola”

Msgid は元の文字列または未翻訳の文字列で、msgstr はローカライズされた文字列です。(最初の行は単なるコメントです)。 PO ファイルには通常、このような多くのブロックが含まれていて (ローカライズする必要がある文字列ごとに 1 つのブロック)、言語ごとに個別の PO ファイルがあります。

一方、MO ファイルは PO ファイルのバイナリ表現で、gettext システムが使用できるように最適化します。翻訳者は PO ファイルを処理し、開発者はアプリケーションをデプロイする前に翻訳された PO ファイルを MO ファイルにコンパイルします。

プロセスの自動化

翻訳プロセスがどのように行われるかがわかったところで、次のステップはそれを自動化することです。幸い、Babel には pybabel と呼ばれるコマンドラインユーティリティがあり、役立ちます。

最初のステップは、ポータブルテンプレート (POT) ファイルとも呼ばれる基本テンプレートとして機能する PO ファイルを生成することです。次のコマンドを参照してください。

$ pybabel extract -F babel.cfg -o messages.pot .

これはソースコードファイルを調べて、gettext() 関数の呼び出しを探し、出現するたびに messages.pot ファイルに書き込まれます。Babel.cfg は、とりわけ、検索プロセスに含めるファイルを決定する設定ファイルです。

次のステップは、アプリケーションをローカライズする言語ごとにこのテンプレートをコピーすることです。次のコマンドを参照してください。

$ pybabel init -i messages.pot -d app/translations -l es

このコマンドは、Babel に messages.pot テンプレートを取得し、スペイン語の翻訳に使用するコピーを作成するように指示しています。フランス語も含める場合は、このコマンドをもう一度入力して、言語として fr を指定します。

結果の PO ファイルを開くと、まだローカライズされていないことがわかります。そのため、通常、ここで翻訳者の出番です。けれども、代わりに Amazon Translate を使用できます。この記事では、generate_translations.py と呼ばれる Python スクリプトを提供します。このスクリプトは、各 PO ファイルを読み取り、すべての文字列に対して Amazon Translate API を 1 回呼び出し、適切な翻訳を行ってファイルを保存します。

次のコードを参照してください。

#!/usr/bin/env python
import argparse
import boto3
import os
import polib
import time

DEFAULT_SOURCE_LANG = 'en'
DEFAULT_TRANSLATIONS_ROOT_DIR = 'app/translations'

def validate_translations_dir(dir):
    """Checks that the given translations directory exists.

    Args:
        dir: The relative path to the directory.

    Raises:
        Exception: The translations directory does not exist.
    """
    if not os.path.exists(dir):
        raise Exception("Translations directory '{}' does not exist".format(dir))

def get_pofile_path_for_language(lang, translations_dir):
    """Returns the relative path to the .PO file for the specified language.

    Args:
        lang: The language code.
        translations_dir: The relative path to the directory containing all
            translations-related files.

    Returns:
        A string with the relative path to the .PO file.
    """
    return "{}/{}/LC_MESSAGES/messages.po".format(translations_dir, lang)

def get_target_languages(translations_dir):
    """Returns all languages that the app should be translated into.

    Args:
        translations_dir: The relative path to the directory containing all
            translations-related files.

    Returns:
        A list of language codes.For example:

        ['es', 'fr']
    """
    target_languages = next(os.walk(translations_dir))[1]
    print('Detected languages:', target_languages)
    return target_languages

def translate_language(src_lang, dest_lang, pofile_path):
    """Translate the app strings from and to the specified languages by
    invoking the Amazon Translate API.

    When this function completes, the translated strings will be written to the
    .PO file of the destination language.

    Args:
        src_lang: The ISO code of the language to translate from, e.g., 'en'.
        dest_lang: The ISO code of the language to translate to, e.g., 'es'.
        pofile_path: The path to the .PO file containing the strings to be
            translated.
    """
    print("Translating from '{}' to '{}'".format(src_lang, dest_lang))

    translate = boto3.client('translate')

    po = polib.pofile(pofile_path)
    for entry in po:
        print("Translating entry '{}' to language '{}'".format(entry.msgid, dest_lang))

        # Calculate the time that the request takes
        time_before = time.time_ns()
        response = translate.translate_text(
            Text=entry.msgid,
            SourceLanguageCode=src_lang,
            TargetLanguageCode=dest_lang
        )
        time_after = time.time_ns()

        entry.msgstr = response['TranslatedText']

        # Wait if needed to avoid throtling
        time_diff = time_after - time_before
        if time_diff < 50000:
            time.sleep(0.05)

    po.save(pofile_path)

def generate_translations_without_cache(src_lang, translations_dir):
    """Translate the app to all applicable target languages, without using
    a cache.

    Args:
        src_lang: The ISO code of the language to translate from, e.g., 'en'.
        translations_dir: The relative path to the directory containing all
            translations-related files.
    """
    target_languages = get_target_languages(translations_dir)

    for dest_lang in target_languages:
        pofile_path = get_pofile_path_for_language(dest_lang, translations_dir)
        translate_language(src_lang, dest_lang, pofile_path)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--source-language",
                        help="the ISO code of the source language to translate from (e.g., 'en'), defaults to '{}'".format(DEFAULT_SOURCE_LANG),
                        default=DEFAULT_SOURCE_LANG)
    parser.add_argument("--translations-dir",
                        help="the relative path to the directory containing all translation data, defaults to '{}'".format(DEFAULT_TRANSLATIONS_ROOT_DIR),
                        default=DEFAULT_TRANSLATIONS_ROOT_DIR)
    args = parser.parse_args()

    print("Source language is:      {}".format(args.source_language))
    print("Translations dir is:     {}".format(args.translations_dir))

    # Check that the translations folder exists.
    validate_translations_dir(args.translations_dir)

    generate_translations_without_cache(args.source_language, args.translations_dir)

It produces the following output:

$ ./generate_translations.py
Source language is:      en
Translations dir is:     app/translations
Detected languages: ['es']
Translating entry 'Hello' to language 'es'

この時点で、PO ファイルの文字列は適切な言語に翻訳されています。

最後に、すべてを MO ファイルにコンパイルする必要があります。ここでも、pybabel を使用できます。次のコマンドを参照してください。

$ pybabel compile -d app/translations/

このステップの後、リクエストの Accept-Language ヘッダーに適切な値が含まれている場合、アプリケーションはコンテンツをスペイン語で表示します。それ以外の場合は、英語にフォールバックします。

CI/CD パイプラインを作成する

ウェブサイトのローカライズプロセスをスクリプト化しましたが、それでも手動でトリガーする必要があります。開発者が新しいコードをチェックインするたびにこれができるとしたらどうでしょうか?

継続的インテグレーション/継続的デリバリー (CI/CD) パイプラインは、このための完璧なソリューションです。名前が示すように、開発チームは CI/CD パイプラインを使用して、コードを自動的にコンパイルし、単体テストと統合テストを実行し、さらに誰かがコードリポジトリに変更をプッシュするたびにアプリケーションをデプロイします。

次の AWS 開発者ツールを使用して、CI/CD パイプラインを構築できます。

  • AWS CodeCommit – Git リポジトリとしてコードをホストします
  • AWS CodePipeline – パイプライン自体を調整します
  • AWS CodeBuild – ビルドコマンドを実行します
  • AWS CodeDeploy – ウェブサイトをデプロイし、ユーザーが利用できるようにします

3 つのステージを持つ基本的なパイプラインを実装できます。次の図は、ソースステージからビルドステージ、デプロイステージまでのこのワークフローを示しています。

ソースステージは、変更が発生するたびに新しいコードをチェックします。デプロイ段階では、新しいバージョンを本番環境にプッシュします。この記事では、サイトを Amazon ECS クラスターにデプロイしますが、Amazon EC2 インスタンス、AWS Lambda、またはオンプレミスサーバーを使用することもできます。

重要な部分はビルド段階です。この段階で実際の翻訳が行われるからです。ビルドの実行に使用するサービスである CodeBuild は、buildspec.yaml というファイルに依存しています。そのファイルには、ビルドプロセスの一部として実行する必要があるすべてのコマンドが含まれています。結果のファイルの内容は次のとおりです。

version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.7
  pre_build:
    commands:
      - pip install -r requirements.txt
  build:
    commands:
      # Extract translation strings from our application code
      - pybabel extract --omit-header -F babel.cfg -o messages.pot .
      # Generate one translation file per applicable language
      - pybabel init -i messages.pot -d app/translations -l es
      # Run our automated translation script
      - ./generate_translations.py
      # Finally, compile all translations so that they can be used by our app
      - pybabel compile -d app/translations/

      # Build and push the Docker image to ECR
      - $(aws ecr get-login --no-include-email --region us-east-1)
      - docker build -t translate-web-app .
      - docker tag translate-web-app:latest ${WEB_APP_IMAGE_URI}:latest
      - docker push ${WEB_APP_IMAGE_URI}:latest
  post_build:
    commands:
      - printf '[{"name":"WebAppContainer", "imageUri":"%s:latest"}]' $WEB_APP_IMAGE_URI > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json

次の 3 つの重要なことが起こっています。

  • 環境に Python 3.7 ランタイムを含める必要があることを宣言しています
  • pre_build フェーズで必要な Python 依存関係 (Babel など) をインストールしています
  • 前述の同じコマンドを適切な順序で呼び出して、ローカライズ版のテキストを生成しています

これで、すべてがどのように組み合わされるかを確認する準備が整いました。現在、ウェブサイトは次のスクリーンショットのようになっており、ウェルカムメッセージは英語で表示されています。

最後の段落の直後に新しいテキスト段落を追加します。

<p>{{ _('This is a newly added sentence.This should appear correctly translated as soon as the build process completes.Amazon Translate makes this very easy!') }}</p>

変更をコミットしてプッシュすると、パイプラインの新しい実行が自動的にトリガーされます。ビルド段階に達したら、CodeBuild コンソールでログを確認できます。翻訳プロセスは問題なく実行され、次の出力が生成されます。

[…]
Translating entry 'Settings' to language 'es'
Translating entry 'Get help' to language 'es'
Translating entry 'Welcome to this site.' to language 'es'
Translating entry 'Use the links on the left to navigate through the site, or sign in using the button at the upper right corner.' to language 'es'
Translating entry 'All text shown in this application is automatically translated as part of your Continuous Integration / Continuous Delivery (CI/CD) flow.' to language 'es'
Translating entry 'This is a newly added sentence.This should appear correctly translated as soon as the build process completes.Amazon Translate makes this very easy!' to language 'es'

ビルドステージが正常に完了したら、デプロイが完了するまで待ちます。それが完了すると、ページを更新した後、新しい文が表示されます。次のスクリーンショットは、テキストが更新されたウェブサイトを示しています。

ただし、ブラウザの言語をスペイン語に変更して再読み込みすると、新しい段落を含め、サイト全体がスペイン語で表示されます。次のスクリーンショットは、この翻訳を示しています。

コストの最適化

ご自身のウェブサイトが希望するだけ多くの言語に自動的にそして楽にローカライズされます、そして Amazon Translate は 50 以上の異なる言語であなたを助けることができます。

ただし、テキストがコミット間で変更されていなくても、ローカリゼーションプロセスは各ビルド中に行われます。すでに翻訳済みのコンテンツを翻訳するには費用がかかるため、これは費用対効果が高くありません。

これを改善するには、ローカライズされた文字列を格納するキャッシュとして Amazon S3 を使用できます。また、ベース POT ファイルのハッシュを保存して、実行ごとに新しい POT ファイルと比較することもできます。ハッシュが一致する場合、POT ファイルは変更されておらず、翻訳をキャッシュからダウンロードできるため、Amazon Translate API を呼び出す必要がありません。すべての翻訳が完了するのを待つ必要がないため、コストが節約され、ビルドプロセスが加速します。これらはすでに PO ファイルに含まれています。

POT ファイルが変更されている場合は、Amazon Translate を使用して、新しい PO ファイルとハッシュを S3 にアップロードできます。これにより、キャッシュが効果的に更新されます。

これで、generate_translations.py スクリプトが更新され、この新機能がサポートされるようになりました。これは次のコードのようになります。

 

#!/usr/bin/env python
import argparse
from botocore.exceptions import ClientError
import boto3
import hashlib
import os
import polib
import time

DEFAULT_SOURCE_LANG = 'en'
DEFAULT_TRANSLATIONS_ROOT_DIR = 'app/translations'

def validate_translations_dir(dir):
    """Checks that the given translations directory exists.

    Args:
        dir: The relative path to the directory.

    Raises:
        Exception: The translations directory does not exist.
    """
    if not os.path.exists(dir):
        raise Exception("Translations directory '{}' does not exist".format(dir))

def validate_cache_bucket(s3_client):
    """Checks that the S3 bucket to be used as the cache exists and can be
    accessed with the AWS credentials available to this script.

    Args:
        s3_client: The boto3 S3 client.

    Raises:
        Exception: The TRANSLATIONS_CACHE_BUCKET enviroment variable is not
        set, or the S3 bucket is either unaccessible or does not exist.
    """
    # Check that the cache bucket actually exists in S3.
    bucket = os.environ['TRANSLATIONS_CACHE_BUCKET']
    try:
        s3_client.head_bucket(Bucket=bucket)
    except ClientError as e:
        raise Exception("The translations cache bucket '{}' does not exist or cannot be accessed".format(bucket))

def get_pofile_path_for_language(lang, translations_dir):
    """Returns the relative path to the .PO file for the specified language.

    Args:
        lang: The language code.
        translations_dir: The relative path to the directory containing all
            translations-related files.

    Returns:
        A string with the relative path to the .PO file.
    """
    return "{}/{}/LC_MESSAGES/messages.po".format(translations_dir, lang)

def get_target_languages(translations_dir):
    """Returns all languages that the app should be translated into.

    Args:
        translations_dir: The relative path to the directory containing all
            translations-related files.

    Returns:
        A list of language codes.For example:

        ['es', 'fr']
    """
    target_languages = next(os.walk(translations_dir))[1]
    print('Detected languages:', target_languages)
    return target_languages

def get_hash_from_file(filename):
    """Calculates and returns an SHA256 hash of the specified file.

    Args:
        filename: The path to the file whose hash is to be calculated.

    Returns:
        An SHA256 hash in hexadecimal representation.
    """
    sha256_hash = hashlib.sha256()
    with open(filename, "rb") as f:
        # Read from file in 4KB blocks, this will allow us to handle large files.
        for byte_block in iter(lambda: f.read(4096), b""):
            sha256_hash.update(byte_block)

    return sha256_hash.hexdigest()

def should_cache_be_used(cache_bucket, s3_client):
    """Determines whether the cache represented by the specified S3 bucket
    is up-to-date and should be used in the translation process.

    The cache is considered to be up-to-date if the bucket contains a file with
    an SHA256 hash of the template file, and this hash matches the one of the
    current template file.

    Args:
        cache_bucket: The name of the S3 bucket to be used as the cache.
        s3_client: The boto3 S3 client.

    Returns:
        A tuple containing, in this order, a boolean indicating whether the
        cache is considered to be up-to-date and the current SHA256 hash of the
        messages file.
    """
    # Calculate the SHA256 hash of the original messages file.
    current_hash = get_hash_from_file('messages.pot')

    # Get the hash of the messages file from the cache bucket.
    use_cache = False
    try:
        hash_response = s3_client.get_object(Bucket=cache_bucket, Key='messages.pot.sha256')
        latest_hash = hash_response['Body'].read().decode('utf-8')

        if latest_hash == current_hash:
            print('Hashes match, will try to use cache first')
            use_cache = True
        else:
            print('Hashes do not match, cache will be skipped')

    except ClientError as e:
        print("'messages.pot.sha256' is not present in bucket")

    return (use_cache, current_hash)

def translate_language(src_lang, dest_lang, pofile_path):
    """Translate the app strings from and to the specified languages by
    invoking the Amazon Translate API.

    When this function completes, the translated strings will be written to the
    .PO file of the destination language.

    Args:
        src_lang: The ISO code of the language to translate from, e.g., 'en'.
        dest_lang: The ISO code of the language to translate to, e.g., 'es'.
        pofile_path: The path to the .PO file containing the strings to be
            translated.
    """
    print("Translating from '{}' to '{}'".format(src_lang, dest_lang))

    translate = boto3.client('translate')

    po = polib.pofile(pofile_path)
    for entry in po:
        print("Translating entry '{}' to language '{}'".format(entry.msgid, dest_lang))

        # Calculate the time that the request takes
        time_before = time.time_ns()
        response = translate.translate_text(
            Text=entry.msgid,
            SourceLanguageCode=src_lang,
            TargetLanguageCode=dest_lang
        )
        time_after = time.time_ns()

        entry.msgstr = response['TranslatedText']

        # Wait if needed to avoid throtling
        time_diff = time_after - time_before
        if time_diff < 50000:
            time.sleep(0.05)

    po.save(pofile_path)

def generate_translations_with_cache(src_lang, translations_dir, cache_bucket):
    """Translate the app to all applicable languages, using the given S3 bucket
    name as cache when possible.

    Args:
        src_lang: The ISO code of the language to translate from, e.g., 'en'.
        translations_dir: The relative path to the directory containing all
            translations-related files.
        cache_bucket: The name of the S3 bucket to be used as the cache.
    """
    s3_client = boto3.client('s3')

    # Check that the cache bucket exists and can be accessed.
    validate_cache_bucket(s3_client)

    # Determine whether we should try to get translations from the cache first.
    # This will save us requests to the Amazon Translate API.
    use_cache, current_hash = should_cache_be_used(cache_bucket, s3_client)

    target_languages = get_target_languages(translations_dir)

    for dest_lang in target_languages:
        pofile_path = get_pofile_path_for_language(dest_lang, translations_dir)
        if use_cache:
            try:
                response = s3_client.get_object(Bucket=cache_bucket, Key="{}/messages.po".format(dest_lang))
                with open(pofile_path, "wb") as f:
                    f.write(response['Body'].read())
                print("Retrieved messages file from cache")
            except ClientError as e:
                print("Cache file not found, will regenerate")
                translate_language(src_lang, dest_lang, pofile_path)
        else:
            translate_language(src_lang, dest_lang, pofile_path)

        # Upload localized messages file to S3 bucket.
        print("Uploading to cache")
        s3_client.put_object(
            Bucket=cache_bucket,
            Key="{}/messages.po".format(dest_lang),
            Body=open(pofile_path, "rb")
        )

    # Update hash in cache bucket, if needed.
    if not use_cache:
        print("Uploading hash")
        s3_client.put_object(
            Bucket=cache_bucket,
            Key='messages.pot.sha256',
            Body=current_hash
        )

def generate_translations_without_cache(src_lang, translations_dir):
    """Translate the app to all applicable target languages, without using
    a cache.

    Args:
        src_lang: The ISO code of the language to translate from, e.g., 'en'.
        translations_dir: The relative path to the directory containing all
            translations-related files.
    """
    target_languages = get_target_languages(translations_dir)

    for dest_lang in target_languages:
        pofile_path = get_pofile_path_for_language(dest_lang, translations_dir)
        translate_language(src_lang, dest_lang, pofile_path)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--source-language",
                        help="the ISO code of the source language to translate from (e.g., 'en'), defaults to '{}'".format(DEFAULT_SOURCE_LANG),
                        default=DEFAULT_SOURCE_LANG)
    parser.add_argument("--translations-dir",
                        help="the relative path to the directory containing all translation data, defaults to '{}'".format(DEFAULT_TRANSLATIONS_ROOT_DIR),
                        default=DEFAULT_TRANSLATIONS_ROOT_DIR)
    parser.add_argument("--no-cache",
                        help="disable the translations cache",
                        action="store_true")
    args = parser.parse_args()

    print("Source language is:      {}".format(args.source_language))
    print("Translations dir is:     {}".format(args.translations_dir))

    # Check that the translations folder exists.
    validate_translations_dir(args.translations_dir)

    if args.no_cache:
        generate_translations_without_cache(args.source_language, args.translations_dir)
    else:
        if 'TRANSLATIONS_CACHE_BUCKET' not in os.environ:
            raise Exception("Environment variable TRANSLATIONS_CACHE_BUCKET is not set, please specify a value")

        generate_translations_with_cache(args.source_language,
                                         args.translations_dir,
                                         os.environ['TRANSLATIONS_CACHE_BUCKET'])

パイプラインを実行すると、キャッシュは空になり、ログには Amazon Translate がどう使用されたかが表示されます。次のように出力されます。

$ ./generate_translations.py
Source language is:      en
Translations dir is:     app/translations
'messages.pot.sha256' is not present in bucket
Detected languages: ['es']
Translating from 'en' to 'es'
Translating entry 'Blog' to language 'es'
Translating entry 'Subscription plans' to language 'es'
Translating entry 'My account' to language 'es'
Translating entry 'Log off' to language 'es'

ただし、 (何も変更せずに) 再度実行すると、代わりにキャッシュが使用されます。次のように出力されます。

$ ./generate_translations.py
Source language is:      en
Translations dir is:     app/translations
Hashes match, will try to use cache first
Detected languages: ['es']
Retrieved messages file from cache

まとめ

この記事は、Amazon Translate を使用して、他の言語を話すユーザーがコンテンツを利用できるようにする簡単で費用効果の高い方法を示しています。Amazon S3 を翻訳キャッシュとして使用してコストを削減でき、CodeCommit、CodeBuild、CodeDeploy、CodePipeline を使用してプロセスを自動化できます。

この記事では Python ベースのウェブサイトを例として使用しましたが、全体的な考え方は変わらないため、手順を他の言語やフレームワークに適合させることができます。


著者について

Carlos Afonso は、マドリードを拠点とするソリューションアーキテクトです。彼はスペインとポルトガルのスタートアップ企業が AWS クラウド上に堅牢でフォールトトレラントで費用対効果の高いアプリケーションを構築するのをサポートしています。AWS について語っていないときは、よく趣味でコーディングをしたり、自作でビールを作ってみたり (出来はまちまちですが) しています。