翻译
翻译翻译

翻译

使用任意提供商的 API 翻译字段值的指令 @strTranslate

描述

为任意 String 类型的字段添加指令 @strTranslate,即可将该字段翻译为目标语言。

例如,以下 Query 使用默认 API 提供商将文章的 titleexcerpt 字段从英语翻译为法语:

{
  posts {
    enTitle: title
    frTitle: title @strTranslate(from: "en", to: "fr")
 
    enExcerpt: excerpt    
    frExcerpt: excerpt @strTranslate(from: "en", to: "fr")
  }
}

...输出结果如下:

{
  "data": {
    "posts": [
      {
        "enTitle": "Welcome to a single post full of blocks!",
        "frTitle": "Bienvenue dans un poste unique plein de blocs !",
        "enExcerpt": "When I look back on my past and think how much time I wasted on nothing, how much time has been lost in futilities, errors, laziness, incapacity to live; how little I appreciated it, how many times I sinned against my heart and soul-then my heart bleeds. Life is a gift, life is happiness, every…",
        "frExcerpt": "Quand je repense à mon passé et que je pense au temps que j'ai perdu pour rien, au temps perdu en futilités, en erreurs, en paresse, en incapacité de vivre ; combien je l'ai peu apprécié, combien de fois j'ai péché contre mon cœur et mon âme, alors mon cœur saigne. La vie est un cadeau, la vie est un bonheur, chaque…"
      },
      {
        "enTitle": "Explaining the privacy policy",
        "frTitle": "Expliquer la politique de confidentialité",
        "enExcerpt": "Our privacy policy is at https://gato-graphql-pro.lndo.site/privacy/, and we are based in Carimano.",
        "frExcerpt": "Notre politique de confidentialité se trouve sur https://gato-graphql-pro.lndo.site/privacy/, et nous sommes basés à Carimano."
      },
      {
        "enTitle": "HTTP caching improves performance",
        "frTitle": "La mise en cache HTTP améliore les performances",
        "enExcerpt": "Categories Block Latest Posts Block Did you know? We are not rich by what we possess but by what we can do without. Patience is the strength of the weak, impatience is the weakness of the strong.",
        "frExcerpt": "Catégories Bloquer les derniers messages Bloquer Le saviez-vous ? Nous ne sommes pas riches de ce que nous possédons mais de ce dont nous pouvons nous passer. La patience est la force du faible, l'impatience est la faiblesse du fort."
      }
    ]
  }
}

Schema Configuration

指令 @strTranslate 需要传入三个参数:

  • provider:用于翻译的提供商
  • from:文本的语言代码
  • to:翻译目标语言代码

可以在设置页面的「Schema Configuration => Translation」选项卡中为这些属性定义默认值。当 Query 中未提供某个参数时,将使用这些默认值:

{
  posts {
    title @strTranslate
  }
}

此外,定义默认值后,GraphQL schema 中对应的参数将变为非必填项。

默认情况下,from 的默认值为 WordPress 中使用的语言。

通过设置页面配置

在设置页面的对应输入框中填写 provider/from/to 字段,然后点击「Save Changes (All)」:

设置默认的 'provider' 以及 'from' 和 'to' 语言
设置默认的 'provider' 以及 'from' 和 'to' 语言

wp-config.php 中配置

wp-config.php 中添加以下常量:

  • GATOGRAPHQL_TRANSLATION_DEFAULT_PROVIDER
  • GATOGRAPHQL_TRANSLATION_DEFAULT_FROM_LANG_CODE
  • GATOGRAPHQL_TRANSLATION_DEFAULT_TO_LANG_CODE

例如:

define( 'GATOGRAPHQL_TRANSLATION_DEFAULT_TO_LANG_CODE', 'fr' );

通过环境变量配置

定义以下环境变量:

  • TRANSLATION_DEFAULT_PROVIDER
  • TRANSLATION_DEFAULT_FROM_LANG_CODE
  • TRANSLATION_DEFAULT_TO_LANG_CODE

同步/异步多语言翻译

指令 @strTranslate 每种语言会发送一个翻译请求。在翻译多种语言时,可以选择以异步(并行)或同步(顺序)方式发送请求。

同步请求与异步请求:

  • 同步: 每个翻译请求在前一个请求完成后才开始。速度较慢,但对频率限制更安全。
  • 异步: 所有翻译请求同时发送。速度更快,但如果同时发送的请求过多,可能会触发频率限制。
使用 sync/async 模式同时翻译多种语言
使用 sync/async 模式同时翻译多种语言

请求超时与连接超时

通过第三方提供商翻译长文档可能会比较耗时,若上游服务挂起,将一直占用 PHP worker,直到 PHP 本身终止该请求。

Web 服务器通过 max_execution_time 指令(在 php.ini 中设置,或通过托管服务的控制面板设置——cPanel 通常在 「Select PHP Version」→「Options」 中提供此选项,SiteGround / Kinsta Engine 等托管服务则在其 PHP 设置中提供)对所有 PHP 请求强制设定最大运行时间。

插件在设置页面的 Plugin Configuration > Translation 下提供了两个选项:

  • Request timeout: 等待翻译提供商返回完整响应的最大时间(秒)
  • Connection timeout: 等待与翻译提供商建立连接的最大时间(秒)
设置翻译的 Request timeout 和 Connection timeout
设置翻译的 Request timeout 和 Connection timeout

这些值应低于服务器的 max_execution_time,这样当翻译卡住时,会以受控的错误信息优雅地失败,而不是触发通用的服务器超时(HTTP 502 / 504,或空白的「Maximum execution time of N seconds exceeded」页面)。如果翻译频繁超时,请同时增大 这两个 值以及服务器的 max_execution_time

调试 API 请求

若要调试发送至翻译提供商(如 ChatGPTClaudeGoogle Translate)的请求及其响应,可在日志设置中启用 🔵 Info 严重级别。

启用后,日志将包含与翻译提供商的所有交互记录,存储在 api-requests 条目下。

日志中的 AI 请求
日志中的 AI 请求

日志记录的内容

对于 AI 提供商,api-requests 条目包含以下详细信息:

  • 发送给翻译提供商的提示词
  • 收到的完整响应
  • 通信过程中发生的任何错误或问题
  • 使用的模型
  • 使用的 token 数量
日志中 AI 请求的详情
日志中 AI 请求的详情

例如,以下「Additional context」JSON 展示了发送给 ChatGPT 的请求及其响应的详细信息:

{
  "request": {
    "model": "gpt-4o-mini",
    "messages": [
      {
        "role": "system",
        "content": "You are a language translator."
      },
      {
        "role": "user",
        "content": "I'm working on internationalizing my application.\n\nI've created a JSON with sentences in English. Please translate the sentences to Spanish from .\n\nIf a sentence contains HTML, do not translate inside the HTML tags. Keep emojis exactly as they are, do not translate them.\n\nThis is the JSON:\n\n[\"Welcome to a single post full of blocks!\",\"Repeating the privacy policy\",\"Explaining the privacy policy\",\"HTTP caching improves performance\",\"Public or Private API mode, for extra security\",\"GraphQL or REST? Why not both?\",\"Customize the schema for each client\",\"Nested mutations are a must have\",\"Working on flat chain syntax next\",\"Released v0.6, check it out\"]"
      }
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "translation_response",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "translations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "required": [
            "translations"
          ],
          "additionalProperties": false
        }
      }
    }
  },
  "response": {
    "id": "chatcmpl-BbjNiuO5Si1vhalfIXYU0hWiCmg12",
    "object": "chat.completion",
    "created": 1748332282,
    "model": "gpt-4o-mini-2024-07-18",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "{\"translations\":[\"¡Bienvenido a una publicación única llena de bloques!\",\"Repitiendo la política de privacidad\",\"Explicando la política de privacidad\",\"La caché HTTP mejora el rendimiento\",\"Modo API Público o Privado, para mayor seguridad\",\"¿GraphQL o REST? ¿Por qué no ambos?\",\"Personaliza el esquema para cada cliente\",\"Las mutaciones anidadas son imprescindibles\",\"Próximamente trabajando en la sintaxis de cadena plana\",\"Lanzada la versión v0.6, ¡échale un vistazo!\"]}",
          "refusal": null,
          "annotations": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 184,
      "completion_tokens": 112,
      "total_tokens": 296,
      "prompt_tokens_details": {
        "cached_tokens": 0,
        "audio_tokens": 0
      },
      "completion_tokens_details": {
        "reasoning_tokens": 0,
        "audio_tokens": 0,
        "accepted_prediction_tokens": 0,
        "rejected_prediction_tokens": 0
      }
    },
    "service_tier": "default",
    "system_fingerprint": "fp_34a54ae93c"
  }
}