mcpField
L
localise

localise

github.com/morphet81开发083 次安装25 周前更新
代码仓库

关于 localise

此技能将英文文本转换为适用于 Lokalise 的交互式 HTML 翻译页面,并提供复制功能。它支持单数与复数输入,并在翻译中保留插值变量。

主要功能

  1. 01生成翻译 HTML 页面
  2. 02处理单数与复数形式
  3. 03保留插值变量
  4. 04复制翻译文本
  5. 05生成配套 Markdown 表格

适用场景

  1. 01为 Lokalise 准备应用文本
  2. 02检查多种目标语言的翻译
  3. 03将译文复制到本地化工作流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/morphet81/cheat-sheets --skill localise

安装后在 Claude Code 中输入/localise确认已加载。

详细介绍

Lokalise 翻译生成器

生成可交互的 HTML 翻译页面,以便将译文粘贴到 Lokalise。

使用场景

  • 用户提供英文字符串(可包含或不包含复数形式),并希望获得可用于 Lokalise 的译文
  • 用户提到要为应用支持的语言翻译某个键
  • 用户需要可快速查阅且支持复制到剪贴板的翻译参考

输入

将 $ARGUMENTS 解析为要翻译的英文文本。格式为 <英文单数形式> [| <英文复数形式>]。

  • 如果 $ARGUMENTS 包含 | 分隔符,则拆分为单数形式(| 之前)和复数形式(| 之后)
  • 如果 $ARGUMENTS 不包含 |,则将整个字符串视为所有语言的单数(other)形式
  • 如果 $ARGUMENTS 为空,则使用 AskUserQuestion 询问用户要翻译的英文文本

用户还可以提供:

  1. 键名(可选):例如 tables::selected_count。如果未提供,则根据英文字符串生成。
  2. 变量:字符串可能包含 %{variable_name} 插值标记。在所有译文中原样保留这些标记。

目标语言(按以下顺序排列)

#代码名称复数形式(Lokalise)RTL
1en英语one, other否
2ar阿拉伯语zero, one, two, few, many, other是
3zh-CN简体中文other否
4zh-TW繁体中文other否
5nl荷兰语one, other否
6fr法语one, other否
7de德语one, other否
8he希伯来语one, other是
9hi印地语one, other否
10id印度尼西亚语other否
11it意大利语one, other否
12ja日语other否
13km高棉语other否
14ko韩语other否
15lo老挝语other否
16ms马来语other否
17pt葡萄牙语one, other否
18ru俄语one, few, many, other否
19es西班牙语one, other否
20tl他加禄语zero, one, two, few, many, other否
21th泰语other否
22tr土耳其语one, other否
23vi越南语other否

翻译指南

  • 在每种翻译中,完整保留所有 %{...} 插值变量。
  • 对于只有 other 形式的语言:提供单个翻译(不区分复数)。
  • 对于有 one/other 形式的语言:在语法适用时提供单数和复数形式。如果该语言的名词形式不变(例如印地语、土耳其语中的某些词),两种形式可以相同。
  • 对于俄语(one/few/many/other):遵循俄语复数变格规则。
  • 对于阿拉伯语(zero/one/two/few/many/other):遵循阿拉伯语复数规则。若表达自然,zero 形式可以省略数量变量(例如“No items selected”)。
  • 对于他加禄语(zero/one/two/few/many/other):遵循他加禄语连接词规则。若表达自然,zero 形式可以省略数量变量。
  • RTL 语言(阿拉伯语、希伯来语)的翻译行需添加 rtl CSS 类。
  • 翻译应符合餐厅/酒店服务 UI 场景,表达自然(TableCheck 产品)。

输出

生成一个独立完整的 HTML 文件。

文件位置

将文件保存到:{working_directory}/.tmp/translations-{sanitized_key}.html

其中 {sanitized_key} 是键名(或从键名派生的 slug),特殊字符替换为连字符。

生成后,使用默认系统浏览器打开该文件:

# macOS
open "{filepath}"
# Linux
xdg-open "{filepath}"
# Try open first, fall back to xdg-open
open "{filepath}" 2>/dev/null || xdg-open "{filepath}"
HTML 结构

生成的 HTML 必须严格遵循以下结构。请精确复制下面的模板,只更改动态内容(键名、翻译)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{KEY_NAME} — Plural Translations</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
  :root {
    --bg: #0e1117;
    --surface: #161b22;
    --surface-2: #1c2129;
    --border: #2d333b;
    --border-light: #373e47;
    --text: #e6edf3;
    --text-muted: #8b949e;
    --accent: #58a6ff;
    --accent-dim: #1a3a5c;
    --green: #3fb950;
    --green-dim: #1a3524;
    --yellow: #d29922;
    --yellow-dim: #3d2e00;
    --purple: #bc8cff;
    --purple-dim: #2a1a4e;
    --orange: #f0883e;
    --red: #f85149;
    --code-bg: #1a1f27;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'DM Sans', sans-serif;
    padding: 40px 32px;
    line-height: 1.6;
  }

  .container {
    max-width: 1360px;
    margin: 0 auto;
  }

  header { margin-bottom: 36px; }

  h1 {
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 6px;
    display: flex;
    align-items: center;
    gap: 10px;
  }

  h1 code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 16px;
    background: var(--accent-dim);
    color: var(--accent);
    padding: 3px 10px;
    border-radius: 6px;
    font-weight: 500;
  }

  .subtitle { color: var(--text-muted); font-size: 14px; }

  .legend { display: flex; gap: 20px; margin-top: 14px; flex-wrap: wrap; }
  .legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); }
  .legend-dot { width: 10px; height: 10px; border-radius: 3px; }
  .legend-dot.one { background: var(--green); }
  .legend-dot.other { background: var(--accent); }
  .legend-dot.few { background: var(--yellow); }
  .legend-dot.many { background: var(--purple); }
  .legend-dot.two { background: var(--orange); }
  .legend-dot.zero { background: var(--red); }

  table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    font-size: 14px;
  }

  thead th {
    background: var(--surface-2);
    color: var(--text-muted);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    padding: 12px 16px;
    text-align: left;
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 2;
  }

  thead th:first-child { width: 50px; }
  thead th:nth-child(2) { width: 140px; }
  thead th:nth-child(3) { width: 100px; }

  tbody tr { transition: background 0.15s; }
  tbody tr:hover { background: var(--surface); }
  tbody tr:not(:last-child) td { border-bottom: 1px solid var(--border); }
  td { padding: 10px 16px; vertical-align: top; }

  .lang-code { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 500; color: var(--accent); }
  .lang-name { font-weight: 500; color: var(--text); font-size: 13px; }
  .lang-name span { color: var(--text-muted); font-weight: 400; font-size: 12px; display: block; }

  .form-tag {
    display: inline-block;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 500;
    padding: 2px 8px;
    border-radius: 4px;
    min-width: 46px;
    text-align: center;
    flex-shrink: 0;
  }

  .form-tag.one { background: var(--green-dim); color: var(--green); }
  .form-tag.other { background: var(--accent-dim); color: var(--accent); }
  .form-tag.few { background: var(--yellow-dim); color: var(--yellow); }
  .form-tag.many { background: var(--purple-dim); color: var(--purple); }
  .form-tag.two { background: #3d1f00; color: var(--orange); }
  .form-tag.zero { background: #3d1116; color: var(--red); }

  .translation-cell { display: flex; flex-direction: column; gap: 6px; }

  .translation-row {
    display: flex;
    align-items: center;
    gap: 10px;
  }

  .translation-text {
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    color: var(--text);
    flex: 1;
  }

  .translation-text .var { color: var(--yellow); }

  .copy-btn {
    flex-shrink: 0;
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-family: 'DM Sans', sans-serif;
    font-size: 11px;
    font-weight: 500;
    padding: 4px 10px;
    border-radius: 5px;
    cursor: pointer;
    transition: all 0.15s;
    display: flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
  }

  .copy-btn:hover {
    background: var(--border);
    color: var(--text);
    border-color: var(--border-light);
  }

  .copy-btn:active { transform: scale(0.96); }

  .copy-btn.copied {
    background: var(--green-dim);
    border-color: var(--green);
    color: var(--green);
  }

  .copy-btn svg { width: 13px; height: 13px; }

  .translation-row.handled {
    background: rgba(248, 81, 73, 0.2);
    border-radius: 4px;
    padding: 2px 6px;
    margin: -2px -6px;
  }

  .rtl .translation-text { direction: rtl; text-align: right; }

  footer {
    margin-top: 24px;
    padding: 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 12px;
    color: var(--text-muted);
    line-height: 1.8;
  }

  footer strong { color: var(--text); font-weight: 600; }
  footer code {
    font-family: 'JetBrains Mono', monospace;
    background: var(--code-bg);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 11px;
  }

  .toast {
    position: fixed;
    bottom: 24px;
    right: 24px;
    background: var(--green-dim);
    border: 1px solid var(--green);
    color: var(--green);
    font-family: 'DM Sans', sans-serif;
    font-size: 13px;
    font-weight: 500;
    padding: 10px 18px;
    border-radius: 8px;
    opacity: 0;
    transform: translateY(10px);
    transition: all 0.25s ease;
    pointer-events: none;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .toast.show { opacity: 1; transform: translateY(0); }
</style>
</head>
<body>
<div class="container">
  <header>
    <h1>Translations for <code>{KEY_NAME}</code></h1>
    <p class="subtitle">23 languages · CLDR plural rules · Variable: <code style="font-family:'JetBrains Mono',monospace;background:var(--yellow-dim);color:var(--yellow);padding:1px 6px;border-radius:4px;font-size:13px">%{count}</code></p>
    <div class="legend">
      <div class="legend-item"><div class="legend-dot one"></div> one</div>
      <div class="legend-item"><div class="legend-dot other"></div> other</div>
      <div class="legend-item"><div class="legend-dot few"></div> few</div>
      <div class="legend-item"><div class="legend-dot many"></div> many</div>
      <div class="legend-item"><div class="legend-dot two"></div> two</div>
      <div class="legend-item"><div class="legend-dot zero"></div> zero</div>
    </div>
  </header>

  <table>
    <thead>
      <tr>
        <th>#</th>
        <th>Language</th>
        <th>Forms</th>
        <th>Translations</th>
      </tr>
    </thead>
    <tbody>
      
    </tbody>
  </table>

  <footer>
    <strong>Notes:</strong><br>
    • These translations should be reviewed by native speakers before production use.<br>
    • Plural forms follow CLDR plural rules as configured in Lokalise.<br>
    • RTL languages (Arabic, Hebrew) are displayed right-to-left.
  </footer>
</div>

<div class="toast" id="toast">
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
  <span id="toast-text">Copied!</span>
</div>

<script>
  const COPY_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
  const CHECK_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="13" height="13"><polyline points="20 6 9 17 4 12"/></svg>';
  let toastTimeout;

  function copyText(btn) {
    const row = btn.closest('.translation-row');
    const textEl = row.querySelector('.translation-text');
    const text = textEl.getAttribute('data-copy');

    navigator.clipboard.writeText(text).then(() => {
      showFeedback(btn, text);
    }).catch(() => {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.cssText = 'position:fixed;opacity:0';
      document.body.appendChild(ta);
      ta.select();
      document.execCommand('copy');
      document.body.removeChild(ta);
      showFeedback(btn, text);
    });
  }

  function showFeedback(btn, text) {
    const row = btn.closest('.translation-row');
    row.classList.add('handled');

    btn.classList.add('copied');
    btn.innerHTML = CHECK_ICON + 'Copied!';
    setTimeout(() => {
      btn.classList.remove('copied');
      btn.innerHTML = COPY_ICON + 'Copy';
    }, 1500);

    const toast = document.getElementById('toast');
    const toastText = document.getElementById('toast-text');
    const display = text.length > 45 ? text.substring(0, 45) + '…' : text;
    toastText.textContent = 'Copied: ' + display;
    clearTimeout(toastTimeout);
    toast.classList.add('show');
    toastTimeout = setTimeout(() => toast.classList.remove('show'), 2000);
  }
</script>
</body>
</html>

行模板

使用以下模板构建每种语言的行。替换 {N}、{LANG_NAME}、{LANG_CODE} 和翻译文本。

复制按钮 HTML(各处重复使用):

<button class="copy-btn" onclick="copyText(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>Copy</button>
单一形式(仅 other)

适用于:zh-CN、zh-TW、id、ja、km、ko、lo、ms、th、vi

<tr>
  <td><span class="lang-code">{N}</span></td>
  <td><span class="lang-name">{LANG_NAME}<span>{LANG_CODE}</span></span></td>
  <td><span class="form-tag other">other</span></td>
  <td class="translation-cell">
    <div class="translation-row">
      <span class="translation-text" data-copy="{PLAIN_TEXT}">{DISPLAY_HTML}</span>
      {COPY_BUTTON}
    </div>
  </td>
</tr>
两种形式(one / other)

适用于:en、nl、fr、de、he、hi、it、pt、es、tr

<tr>
  <td><span class="lang-code">{N}</span></td>
  <td><span class="lang-name">{LANG_NAME}<span>{LANG_CODE}</span></span></td>
  <td><span class="form-tag one">one</span> <span class="form-tag other">other</span></td>
  <td class="translation-cell">
    <div class="translation-row{RTL_CLASS}">
      <span class="form-tag one">one</span>
      <span class="translation-text" data-copy="{PLAIN_ONE}">{DISPLAY_ONE}</span>
      {COPY_BUTTON}
    </div>
    <div class="translation-row{RTL_CLASS}">
      <span class="form-tag other">other</span>
      <span class="translation-text" data-copy="{PLAIN_OTHER}">{DISPLAY_OTHER}</span>
      {COPY_BUTTON}
    </div>
  </td>
</tr>
四种形式(one / few / many / other)

适用于:ru

<tr>
  <td><span class="lang-code">{N}</span></td>
  <td><span class="lang-name">Russian<span>ru</span></span></td>
  <td><span class="form-tag one">one</span> <span class="form-tag few">few</span> <span class="form-tag many">many</span> <span class="form-tag other">other</span></td>
  <td class="translation-cell">
    <div class="translation-row">
      <span class="form-tag one">one</span>
      <span class="translation-text" data-copy="{PLAIN}">{DISPLAY}</span>
      {COPY_BUTTON}
    </div>
    <div class="translation-row">
      <span class="form-tag few">few</span>
      <span class="translation-text" data-copy="{PLAIN}">{DISPLAY}</span>
      {COPY_BUTTON}
    </div>
    <div class="translation-row">
      <span class="form-tag many">many</span>
      <span class="translation-text" data-copy="{PLAIN}">{DISPLAY}</span>
      {COPY_BUTTON}
    </div>
    <div class="translation-row">
      <span class="form-tag other">other</span>
      <span class="translation-text" data-copy="{PLAIN}">{DISPLAY}</span>
      {COPY_BUTTON}
    </div>
  </td>
</tr>
六种形式(zero / one / two / few / many / other)

适用于:ar、tl

<tr>
  <td><span class="lang-code">{N}</span></td>
  <td><span class="lang-name">{LANG_NAME}<span>{LANG_CODE}</span></span></td>
  <td><span class="form-tag zero">zero</span> <span class="form-tag one">one</span> <span class="form-tag two">two</span> <span class="form-tag few">few</span> <span class="form-tag many">many</span> <span class="form-tag other">other</span></td>
  <td class="translation-cell">
    
    
    <div class="translation-row{RTL_CLASS}">
      <span class="form-tag zero">zero</span>
      <span class="translation-text" data-copy="{PLAIN}">{DISPLAY}</span>
      {COPY_BUTTON}
    </div>
    
  </td>
</tr>

显示 HTML 格式

在 {DISPLAY} 内容中,使用黄色高亮 span 包裹所有 %{variable} 标记:

  • 纯文本(用于 data-copy):%{count} tables selected
  • 显示 HTML(用于可见内容):<span class="var">%{count}</span> tables selected

Markdown 文件

除 HTML 文件外,还需生成一个包含所有翻译的配套 Markdown 文件,并以表格形式呈现。

文件位置

将文件保存到:{working_directory}/.tmp/translations-{sanitized_key}.md

(与 HTML 文件位于同一目录,文件名相同,仅扩展名为 .md。)

Markdown 结构

该文件必须严格遵循以下结构:

# Translations: `{KEY_NAME}`

| # | Language | Code | Form | Translation |
|---|----------|------|------|-------------|
| 1 | English | en | one | %{count} table selected |
| 1 | English | en | other | %{count} tables selected |
| 2 | Arabic | ar | zero | ... |
| 2 | Arabic | ar | one | ... |
| 2 | Arabic | ar | two | ... |
| 2 | Arabic | ar | few | ... |
| 2 | Arabic | ar | many | ... |
| 2 | Arabic | ar | other | ... |
| 3 | Chinese (Simplified) | zh-CN | other | ... |
...

规则:

  • 语言顺序必须与 HTML 中相同(1-23)。
  • 每种复数形式各占一行。
  • 同一种语言的所有形式使用相同的行号(例如,阿拉伯语的所有形式都使用 2)。
  • Translation 列包含纯文本(保留 %{...} 变量,不含 HTML 标记)。
  • 对于只有一种形式(仅 other)的语言,仅需一行。
  • 对于有多种形式的语言,每种形式各占一行,并按以下顺序排列:zero、one、two、few、many、other(仅包含该语言适用的形式)。

保存前检查清单

  1. 包含全部 23 种语言,且顺序完全符合指定要求
  2. 各语言的复数形式正确(不使用通用 CLDR 规则,而使用上方 Lokalise 形式表)
  3. 所有翻译中均完整保留 %{...} 变量
  4. data-copy 属性仅包含纯文本(不含 HTML)
  5. 显示内容中的变量均使用 <span class="var"> 包裹
  6. 阿拉伯语和希伯来语行均应用 RTL 类
  7. 行号按 1-23 顺序排列
  8. 每个翻译行均包含复制按钮
  9. HTML 和 Markdown 文件均已保存到 .tmp/ 文件夹
  10. 已在默认浏览器中打开 HTML 文件

评论

支持 Markdown