MJML

使用 Lingo.dev CLI 对 MJML 邮件模板进行 AI 翻译

什么是 MJML?

MJML(Mailjet Markup Language)是一套响应式邮件框架,可帮助开发者轻松创建响应式邮件模板。它采用语义化语法,简化了响应式邮件的开发流程,并可编译为兼容所有主流邮件客户端的标准 HTML。

什么是 Lingo.dev CLI?

Lingo.dev CLI 是一款免费、开源的命令行工具,支持通过 AI 翻译应用和内容。它旨在取代传统的翻译管理软件,并可集成到现有的开发流水线中。

如需了解更多,请参阅 概述

关于本指南

本指南将介绍如何使用 Lingo.dev CLI 翻译 MJML 邮件模板。

你将学到如何:

  • 从零创建项目
  • 配置翻译流水线
  • 使用 AI 生成翻译内容

前置条件

要使用 Lingo.dev CLI,请确保已安装 Node.js v18 及以上版本:

❯ node -v
v22.17.0

第 1 步:创建项目

在你的项目目录下,创建一个 i18n.json 文件:

{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {}
}

该文件用于定义翻译流水线的行为,包括需要翻译的语言以及本地化内容在文件系统中的位置。

如需了解可用属性,请参阅 i18n.json

第 2 步:配置源语言环境

源语言环境 指内容最初编写时所用的语言和地区。要配置源语言环境,请在 i18n.json 文件中设置 locale.source 属性:

{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {}
}

源语言环境必须采用 BCP 47 语言标签 格式。

如需查看 Lingo.dev CLI 支持的全部语言环境代码,请参阅 支持的语言环境代码

第 3 步:配置目标语言环境

目标语言环境 指的是您希望将内容翻译成的语言和地区。要配置目标语言环境,请在 locale.targets 属性中设置 i18n.json 文件:

{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {}
}

第 4 步:创建源内容

如果尚未创建,请新建一个或多个包含待翻译内容的 MJML 模板文件。这些文件的路径中必须包含源语言环境(例如,作为目录名 en/,或作为文件名的一部分 example_en.mjml)。

对于 MJML 模板,可翻译内容包括:

  • MJML 组件中的文本内容:
    • mj-text(正文)
    • mj-button(按钮标签)
    • mj-title(预览中的邮件标题)
    • mj-preview(预览文本)
    • mj-navbar-link(导航链接)
    • mj-accordion-title(手风琴标题)
    • mj-accordion-text(手风琴内容)
  • MJML 中的 HTML 元素:ph1-h6li
  • 属性值,包括:
    • alttitle 属性(在 mj-image 上)
    • titlearia-label 属性(在 mj-button 上)
    • titlealt 属性(在 mj-social-element 上)
    • HTML imga 元素上的 alttitle 属性

内联 HTML 的处理方式:

当文本包含内联 HTML 元素(如 <strong><span><em><a> 等)时,整个文本块会作为一个完整单元进行翻译。这可以保留上下文信息,提高翻译质量,并保持内联格式。

例如:

<mj-text>
  <p>Welcome to <strong>our platform</strong>!</p>
</mj-text>

整个段落 "Welcome to <strong>our platform</strong>!" 会作为一个整体进行翻译,并保留 <strong> 标签。这样可以确保 AI 翻译器拥有完整上下文,并在翻译中保留内联格式。

模板变量(如 Razor 变量 @Model.UserName)在翻译过程中会被保留。

步骤 5. 创建 bucket

  1. i18n.json 文件中,向 buckets 对象添加一个 "mjml" 对象:

    {
      "$schema": "https://lingo.dev/schema/i18n.json",
      "version": "1.10",
      "locale": {
        "source": "en",
        "targets": ["es"]
      },
      "buckets": {
        "mjml": {}
      }
    }
    
  2. "mjml" 对象中,定义一个包含一个或多个 include 模式的数组:

    {
      "$schema": "https://lingo.dev/schema/i18n.json",
      "version": "1.10",
      "locale": {
        "source": "en",
        "targets": ["es"]
      },
      "buckets": {
        "mjml": {
          "include": ["./[locale]/example.mjml"]
        }
      }
    }
    

    这些模式用于定义需要翻译的文件。

    模式本身:

    • 必须包含 [locale] 作为已配置语言区域的占位符
    • 可以指向文件路径(如 "[locale]/example.mjml"
    • 可以使用星号作为通配符(如 "[locale]/*.mjml"

    不支持递归 glob 模式(如 **/*.mjml)。

步骤 6. 配置 LLM

Lingo.dev CLI 使用大型语言模型(LLM)通过 AI 翻译内容。要使用这些模型之一,您需要从支持的服务商获取 API 密钥。

为了尽快开始使用,我们推荐使用 Lingo.dev Engine

  1. 注册 Lingo.dev 账号

  2. 运行以下命令:

    npx lingo.dev@latest login
    

    这将打开您的默认浏览器,并要求您进行身份验证。

  3. 按照提示操作。

步骤 7. 生成翻译内容

在包含 i18n.json 文件的目录下,运行以下命令:

npx lingo.dev@latest run

该命令将:

  1. 读取 i18n.json 文件。
  2. 查找需要翻译的文件。
  3. 从文件中提取可翻译内容。
  4. 使用已配置的 LLM 翻译提取的内容。
  5. 将翻译后的内容写回文件系统。

首次生成翻译时,会创建一个 i18n.lock 文件。该文件用于记录已翻译的内容,防止后续运行时重复翻译。

示例

en/example.mjml

<?xml version="1.0" encoding="UTF-8"?>
<mjml>
  <mj-head>
    <mj-title>Welcome to Our Service</mj-title>
    <mj-preview>Get started with your new account today</mj-preview>
    <mj-attributes>
      <mj-all font-family="Arial, sans-serif" />
    </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section background-color="#f0f0f0">
      <mj-column>
        <mj-image
          src="https://example.com/logo.png"
          alt="Company Logo"
          width="150px"
        />
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-text font-size="24px" color="#333333" font-weight="bold">
          Welcome to Our Platform!
        </mj-text>
        <mj-text font-size="16px" color="#555555" line-height="24px">
          Thank you for signing up. We're excited to have you on board.
        </mj-text>
        <mj-text font-size="16px" color="#555555" line-height="24px">
          To get started, please verify your email address by clicking the
          button below.
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-button
          background-color="#007bff"
          color="#ffffff"
          href="https://example.com/verify"
          title="Verify your email address"
          aria-label="Verify email"
        >
          Verify Email Address!
        </mj-button>
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-text font-size="14px" color="#666666">
          If you didn't create an account, you can safely ignore this email.
        </mj-text>
        <mj-text font-size="14px" color="#666666">
          Need help? Contact our support team.
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#f8f9fa" padding="20px">
      <mj-column>
        <mj-social mode="horizontal">
          <mj-social-element
            name="facebook"
            href="https://facebook.com/example"
            title="Follow us on Facebook"
            alt="Facebook"
          />
          <mj-social-element
            name="twitter"
            href="https://twitter.com/example"
            title="Follow us on Twitter"
            alt="Twitter"
          />
          <mj-social-element
            name="instagram"
            href="https://instagram.com/example"
            title="Follow us on Instagram"
            alt="Instagram"
          />
        </mj-social>
        <mj-text font-size="12px" color="#999999" align="center">
          © 2024 Example Company. All rights reserved.
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

es/example.mjml

<?xml version="1.0" encoding="UTF-8"?>
<mjml>
  <mj-head>
    <mj-title>Bienvenido a nuestro servicio</mj-title>
    <mj-preview>Comienza con tu nueva cuenta hoy</mj-preview>
    <mj-attributes>
      <mj-all font-family="Arial, sans-serif" />
    </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section background-color="#f0f0f0">
      <mj-column>
        <mj-image
          src="https://example.com/logo.png"
          alt="Logo de la empresa"
          width="150px"
        />
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-text font-size="24px" color="#333333" font-weight="bold">
          ¡Bienvenido a nuestra plataforma!
        </mj-text>
        <mj-text font-size="16px" color="#555555" line-height="24px">
          Gracias por registrarte. Estamos encantados de tenerte con nosotros.
        </mj-text>
        <mj-text font-size="16px" color="#555555" line-height="24px">
          Para comenzar, por favor verifica tu dirección de correo electrónico
          haciendo clic en el botón de abajo.
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-button
          background-color="#007bff"
          color="#ffffff"
          href="https://example.com/verify"
          title="Verifica tu dirección de correo electrónico"
          aria-label="Verificar correo"
        >
          ¡Verificar dirección de correo!
        </mj-button>
      </mj-column>
    </mj-section>

    <mj-section background-color="#ffffff" padding="20px">
      <mj-column>
        <mj-text font-size="14px" color="#666666">
          Si no creaste una cuenta, puedes ignorar este correo electrónico.
        </mj-text>
        <mj-text font-size="14px" color="#666666">
          ¿Necesitas ayuda? Contacta a nuestro equipo de soporte.
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#f8f9fa" padding="20px">
      <mj-column>
        <mj-social mode="horizontal">
          <mj-social-element
            name="facebook"
            href="https://facebook.com/example"
            title="Síguenos en Facebook"
            alt="Facebook"
          />
          <mj-social-element
            name="twitter"
            href="https://twitter.com/example"
            title="Síguenos en Twitter"
            alt="Twitter"
          />
          <mj-social-element
            name="instagram"
            href="https://instagram.com/example"
            title="Síguenos en Instagram"
            alt="Instagram"
          />
        </mj-social>
        <mj-text font-size="12px" color="#999999" align="center">
          © 2024 Example Company. Todos los derechos reservados.
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

i18n.json

{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {
    "mjml": {
      "include": ["./[locale]/example.mjml"]
    }
  }
}

i18n.lock

version: 1
checksums:
  c1acde0589961652d4caf8a39d080857:
    mjml/mj-head/0/mj-title/0: c514a686b50f7158b2dd08ea65d3bc8a
    mjml/mj-head/0/mj-preview/0: 4ce14f6062c814cbdcdf8b0a3cb094d3
    mjml/mj-body/0/mj-section/0/mj-column/0/mj-image/0#alt: 82d5c0d5994508210ee02d684819f4b8
    mjml/mj-body/0/mj-section/1/mj-column/0/mj-text/0: b320b02942617a70dcbd1beac61da11a
    mjml/mj-body/0/mj-section/1/mj-column/0/mj-text/1: 028311348a5aeefea365fdf422a3fb21
    mjml/mj-body/0/mj-section/1/mj-column/0/mj-text/2: 0dfdc9b80ee70fcc2b28d0e81e03fabc
    mjml/mj-body/0/mj-section/2/mj-column/0/mj-button/0#title: 5c96f738bd6153ee07b72094cdfd2b98
    mjml/mj-body/0/mj-section/2/mj-column/0/mj-button/0#aria-label: 42dcab68d931f9145d9b6d76740a5c66
    mjml/mj-body/0/mj-section/2/mj-column/0/mj-button/0: dc8001d5c58294d22fe0b0e6118dbfb7
    mjml/mj-body/0/mj-section/3/mj-column/0/mj-text/0: a18f14ab69467cbdbe467df6255cfda7
    mjml/mj-body/0/mj-section/3/mj-column/0/mj-text/1: e83236e98aad1937bc99a47cff159caa
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/0#title: 180bd8aa700f6cedf65e0a2079503cea
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/0#alt: ac8afe226a7424849c247e6a9d566f64
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/1#title: ea4c2a7a9a60cbb0f8f9632222a46abe
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/1#alt: ba3d4aed69a50759b53a0b7c319a3ad9
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/2#title: 754efa5f98f51c510ff268e217877d8b
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-social/0/mj-social-element/2#alt: c9555810826c30d571ffae869a236494
    mjml/mj-body/0/mj-section/4/mj-column/0/mj-text/0: 9ac6c625c7af33d70634846c8c9d11b0