关于 localise
此技能将英文文本转换为适用于 Lokalise 的交互式 HTML 翻译页面,并提供复制功能。它支持单数与复数输入,并在翻译中保留插值变量。
主要功能
- 01生成翻译 HTML 页面
- 02处理单数与复数形式
- 03保留插值变量
- 04复制翻译文本
- 05生成配套 Markdown 表格
适用场景
- 01为 Lokalise 准备应用文本
- 02检查多种目标语言的翻译
- 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询问用户要翻译的英文文本
用户还可以提供:
- 键名(可选):例如
tables::selected_count。如果未提供,则根据英文字符串生成。 - 变量:字符串可能包含
%{variable_name}插值标记。在所有译文中原样保留这些标记。
目标语言(按以下顺序排列)
| # | 代码 | 名称 | 复数形式(Lokalise) | RTL |
|---|---|---|---|---|
| 1 | en | 英语 | one, other | 否 |
| 2 | ar | 阿拉伯语 | zero, one, two, few, many, other | 是 |
| 3 | zh-CN | 简体中文 | other | 否 |
| 4 | zh-TW | 繁体中文 | other | 否 |
| 5 | nl | 荷兰语 | one, other | 否 |
| 6 | fr | 法语 | one, other | 否 |
| 7 | de | 德语 | one, other | 否 |
| 8 | he | 希伯来语 | one, other | 是 |
| 9 | hi | 印地语 | one, other | 否 |
| 10 | id | 印度尼西亚语 | other | 否 |
| 11 | it | 意大利语 | one, other | 否 |
| 12 | ja | 日语 | other | 否 |
| 13 | km | 高棉语 | other | 否 |
| 14 | ko | 韩语 | other | 否 |
| 15 | lo | 老挝语 | other | 否 |
| 16 | ms | 马来语 | other | 否 |
| 17 | pt | 葡萄牙语 | one, other | 否 |
| 18 | ru | 俄语 | one, few, many, other | 否 |
| 19 | es | 西班牙语 | one, other | 否 |
| 20 | tl | 他加禄语 | zero, one, two, few, many, other | 否 |
| 21 | th | 泰语 | other | 否 |
| 22 | tr | 土耳其语 | one, other | 否 |
| 23 | vi | 越南语 | 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 语言(阿拉伯语、希伯来语)的翻译行需添加
rtlCSS 类。 - 翻译应符合餐厅/酒店服务 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(仅包含该语言适用的形式)。
保存前检查清单
- 包含全部 23 种语言,且顺序完全符合指定要求
- 各语言的复数形式正确(不使用通用 CLDR 规则,而使用上方 Lokalise 形式表)
- 所有翻译中均完整保留
%{...}变量 data-copy属性仅包含纯文本(不含 HTML)- 显示内容中的变量均使用
<span class="var">包裹 - 阿拉伯语和希伯来语行均应用 RTL 类
- 行号按 1-23 顺序排列
- 每个翻译行均包含复制按钮
- HTML 和 Markdown 文件均已保存到
.tmp/文件夹 - 已在默认浏览器中打开 HTML 文件