关于 create-app
create-app 是一项用于指导构建 Crowdin 应用的技能,面向翻译人员和本地化管理者的需求。它引导 Agent 查阅权威文档、厘清应用放置位置与可见范围,并按步骤完成构建和交付。
主要功能
- 01引导以非技术语言澄清需求
- 02指导检查 Crowdin 文档与平台限制
- 03规划应用位置、逻辑和可见范围
- 04说明前端应用的数据存储边界
- 05提供从预检到交付的构建流程
适用场景
- 01为术语查询需求规划 Crowdin 应用
- 02将手动整理的本地化数据表改为应用界面
- 03根据 Crowdin 页面链接确定版本和项目
- 04判断定时邮件或外部调用需求的可行替代方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ crowdin/ skills --skill create-app安装后在 Claude Code 中输入/create-app确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/cli.mdmodules.mdscopes.mdsdk.md详细介绍
创建 Crowdin 应用
谁提出了需求
译者或本地化经理。他们带来的不是规格说明,而是一件早已厌烦的琐事:“告诉我以前是怎么翻译这个术语的”“我想看看校对员之后改了什么”“我每周四都要手动制作这份电子表格”。
他们不知道什么是模块、清单、权限范围、编辑器模式或项目标识符,也不知道这个平台能做什么、不能做什么。他们不会事先检查自己的想法是否可实现,也不应该需要这么做。**你向他们提出的每个问题,都必须是从未看过一行代码的人也能回答的。**询问模块类型、权限范围、清单、模板、编辑器模式、项目 ID 或版本,说明这个工作流程有缺陷,而不是用户有所欠缺。
当他们的需求超出平台的能力范围时,该由你来解决,而不是让他们自己想办法避开。请参阅平台说不时。
你实际要构建的内容
一个仅包含前端的 React 包,Crowdin 会在自己的 UI 中通过 iframe 加载它。应用没有后端,也没有令牌:宿主会代你执行 Crowdin REST 调用,并保存应用的持久化记录(Crowdin Storage),使用打开应用的用户的会话,且权限受清单中声明的权限范围限制。
信息来源
随着时间推移,平台会不断增加模块类型、清单字段、SDK 方法和权限范围。这项技能无法跟上,训练数据也一样——因此,想了解现有功能,请查阅文档。每个页面都有机器可读版本:在任意文档 URL 后添加 .md,或者一次性获取全部文档。
| 获取内容 | 用途 |
|---|---|
| building-app/modules.md | 当前的模块类型及其 prepare* 函数 |
| reference/manifest.md | 清单字段、模块结构、权限 |
| reference/cli-commands.md | 已发布的命令和标志 |
| building-app/crowdin-api.md | 无令牌 API 客户端 |
| building-app/storage.md | Crowdin Storage——持久化的共享记录、用户级记录和模块范围记录 |
| building-app/context.md | 上下文、主题、事件 |
| building-app/host-actions.md | 编辑器和宿主操作 |
| building-app/user-interface.md | UI 套件 |
| building-app/i18n.md | 本地化 |
| development/troubleshooting.md | 已知故障 |
| understanding-scopes | 各项权限范围授予的权限 |
| llms-full.txt | 一次获取全部内容 |
分工如下:**文档是现有功能的权威来源,这项技能说明该按什么顺序操作,以及哪些地方容易出问题。**如果这里的列表与文档不一致,以文档为准,这里的列表已经过时——即使这些页面没有提到某项能力,只要文档有说明,就可以使用。当某项功能可用,却以文档未提及的方式出问题时,这正是这项技能的用武之地。
必须达到的目标
终点不是“写完代码”,甚至也不是“发布应用”——而是用户正看着自己可正常运行的应用。在你交付一个有理由相信能正常显示的链接之前,或对于没有链接的展示位置,在他们确认自己能看到之前,都应视为流程尚未完成。
首先值得问的唯一问题
询问链接:
可以发我一个 Crowdin 页面链接吗?可以是你使用的项目,也可以只是你的主页。
任何人都能从地址栏复制 URL,这可以回答另外两件你无法推断的事:
- 使用哪个版本。 主机名为
crowdin.com表示 Crowdin;其他主机名(<org>.crowdin.com)表示 Crowdin Enterprise。这决定了他们想要的展示位置是否存在——十一种可打开的模块类型中,有六种只存在于某一个版本中。 - 使用哪个项目。
crowdin.com/project/<identifier>或<org>.crowdin.com/u/projects/<id>会给出preview --project所需的值,无需询问“标识符”。
如果对方没有提供链接,CLI 自己的状态文件会在登录后保存版本信息:~/.crowdin/serverless-apps-cli.json。其中,非空的 domain 表示 Enterprise;如果没有该字段,则表示 crowdin.com。
创建脚手架前先确定四件事
| 项目 | 为什么必须尽早确定 |
|---|---|
| 名称 | create <name> 会为目录命名、注册应用,并在 UI 中为应用及其所有模块命名。传入简短的拉丁字母 slug;如果用户想要更易读的名称,则之后在清单中设置 name 并推送。 |
| 展示位置 | 应用在 Crowdin UI 中的显示位置。这是最容易出错的事项,而且受版本限制(modules.md)。 |
| 逻辑 | 他们期望看到什么结果,这决定了 UI、API 调用,进而决定 scopes。 |
| 谁能看到 | default_permissions。默认值仅为 owner,对大多数请求来说都不合适,而且之后无法扩大权限——请参阅可见性。 |
谨慎提问,只用他们的说法
推断,不要问: 用户是谁、应用会访问哪些数据、scopes(这是通过绿色关卡后的结果,不是需求说明的输入——见 scopes.md)、版本和项目(根据链接推断)、编辑器 modes(默认声明文档列出的所有模式,这样无论用户在哪工作,面板都会显示——只有用户提出要求时才缩小范围,因为模块不会出现在清单未包含的模式中,用户也无从知道原因),以及合适的应用名称。模板固定不变:始终使用 projects-dashboard-cli。
陈述,不要问: 将所有推断出的内容写成一段简短说明,让他们纠正。用他们写信时使用的语言,并使用直白的措辞——“编辑器右侧、字符串旁边的面板”,而不是“editor-right-panel,modes translate/review”。与问题列表相比,陈述列表更容易让人指出异议。
仅在以下情况下提问:
- 无法从请求中推断出展示位置。 “在编辑器中字符串旁边”已经足够明确。“在项目中”则存在实际选择:Tools 部分还是 Reports 部分——应说明这两个位置,并给出建议后再询问。
- 两种实现会让用户看到不同内容。 不要问“应该怎么做”,而要问“进度应该按语言显示为图表、按文件显示为表格,还是只显示一个数字?”。
- 平台迫使他们在不同方案之间取舍。 见下文。
一次只问一个问题,并附上你的建议答案,这样回复“是”就足够了。如果他们不知道,就采用你的建议并继续。一个问题都不问,也完全没问题。
然后展示说明并等待对方同意:create 会在他们的组织中注册一个真实应用,而且没有 delete 命令。
当应用需要记住某些内容时
Crowdin Storage 是应用自带的持久化键值存储,由 Crowdin 按安装实例托管,因此无需后端即可构建存储设置、重新加载后仍保留的备注,以及团队共享的状态。它需要在清单的 scopes 中添加 application.storage,调用方式与 REST 代理类似:以当前查看者的身份调用,无需令牌。相关接口说明见 构建应用/storage.md;参考/sdk.md 中包含经过验证的代码片段。
设计时要决定的不是如何存储,而是每条记录属于谁,键前缀正是用于确保归属的机制:
| 数据是 | 使用 | 原因 |
|---|---|---|
| 某个用户自己的数据——其筛选条件、草稿、备注、输入的凭据 | kv.user.*(user: 前缀) | 平台会将其隐藏:对于调用者来说,其他用户的记录根本不存在,无论是读取还是列出记录都是如此 |
| 归属于应用某个界面的数据——团队状态、集成设置、该界面团队的凭据 | module:{moduleKey}: 下的键 | 平台只会在应用以该模块运行、且查看者获准访问的界面上提供这些数据——仅限管理员的模块中的记录绝不会传到应用的其他界面 |
| 对应用可触达的所有人公开,或由应用的模块共享的数据 | 普通键 | 没有任何保护:所有能使用应用的人都可以读取——在公开项目中也包括匿名访客——而且所有已登录的查看者都可以覆盖这些数据 |
与个人相关的内容默认使用 user:,由某个界面团队拥有的内容默认使用 module:,因为这两种方式都由平台进行访问控制;普通共享键适用于应用可见的任何人都可以合理查看、且登录后可以更改的值——包括应用的各个模块必须共享的数据,因为 module: 记录不会跨界面共享。只有一个人不受模块限制:应用安装者可在任何界面查看所有模块的记录——只有其他用户的 user: 记录仍对其隐藏。使用顺序很重要,因为故障不会显现:只有一个人测试应用时,普通键的行为完全正常;直到第二个用户加入,数据才会泄露或被覆盖。
有两个事实会影响设计:未通过身份验证的查看者没有用户身份,因此对他们使用 kv.user.* 会引发错误,而共享键仍可正常工作——提供按用户区分的功能前,请检查上下文中的 user.id;卸载应用会立即删除所有记录,不会保留。
当平台说不行时
纯前端应用确实无法做到三件事:
- 使用密钥而不泄露它。 没有后端,因此使用凭据的代码会在查看者的浏览器中运行——写入构建包的密钥可被任何查看者读取,而存储记录解密后会返回给其权限范围内的所有人,不管是否设置了
secret: true。只有当所有能访问它的人都可以知道它时,才适合存储:每位用户为自己输入、存放在kv.user.*下的凭据,或仅该团队可使用的模块在module:前缀下存放的团队集成凭据——这两种情况都设置secret: true。必须供不应看到密钥的人使用的密钥,应放在 Crowdin 自己的集成设置中,而不是应用里。 - 接受外部调用。 Webhook、计划任务、电子邮件,以及 Crowdin 通过 HTTP 调用的平台代理扩展点(
custom-mt、custom-file-format、ai-provider、external-qa-check等——它们甚至不是这里的有效模块类型)。关闭的标签页什么也听不到。 - 在标签页关闭后继续运行。 在浏览器中处理几 MB 的文件没问题;几百 MB 就不行了。
说不绝不是工作流程的终点。 用户带着实际任务而来,不能只告诉他们“这需要后端”。交付可行的部分,并用一句直白的话说明取舍:
| 他们的需求 | 提供给他们 |
|---|---|
| 每周邮件摘要 | 页面本身,他们需要时可以打开,再附上 Crowdin 自己的通知设置入口。说明这类应用无法发送邮件 |
| 第三方引擎或密钥 | 如果密钥必须供不应看到它的人使用,提供 Crowdin 自己的该集成设置。如果所有使用者也可以知道它,则 Crowdin Storage 可以通过 secret: true 存储:个人密钥使用 kv.user.*,团队密钥使用受限模块的 module: 前缀 |
| 处理超大文件 | 处理大小合适的文件,并提前商定限制 |
只有在没有任何有用的部分可以保留时,才说明无法完成,并提出交给开发者处理。绝不要让用户以 npm 包名作为接下来的步骤结束工作流程。
构建流程
0. 预检
Node ^22.19 || ^24 和 pnpm。npx 需要 Node,因此通常缺少的是 pnpm:corepack enable pnpm。如果缺少 Node,要直说——需要先安装一次——并提供帮助,而不是把它变成用户必须独自满足的要求。
通过只读调用检查身份验证:
npx @crowdin/serverless-apps-cli@latest list --json
返回 JSON 表示已登录。返回 Not logged in. Run crowdin-serverless-apps login first. 时,先执行身份验证流程。
创建应用前,先用绝对路径说明应用将创建的位置。之后的所有更改都在该目录中进行,用户需要知道自己的应用在哪里。
1. 搭建项目
npx @crowdin/serverless-apps-cli@latest create <name> --template projects-dashboard-cli --yes
cd <name> && pnpm install
两个标志都很重要:缺少它们时,CLI 会等待交互式选择器,而它在非交互式 shell 中会卡住。
始终使用 projects-dashboard-cli。 模板不是需要权衡的选择,也不值得为此提问。使用该模板时,CLI 会负责整套工具链——Vite、Tailwind、Lingui、Biome 和基础 tsconfig——因此后续所有内容都能按此处所述正常工作。projects-dashboard-standalone 会把构建交回给应用,而之后出现的每个相关问题都将由你负责;只有用户明确要求自行负责构建时才选择它。
create 会将 CROWDIN_APP_ID 写入 .env 并注册应用。从现在起,在应用目录中使用本地二进制文件——npx crowdin-serverless-apps <command>——以确保工具链与应用固定的版本一致。
2. 放置位置:悄无声息地出问题的环节
脚手架附带两个模块:organization-menu 和 profile-resources-menu,而且它们各自只存在于一个版本中。无论用户使用哪个版本,这两个模块中总有一个不可用:它永远不会显示,preview 也拒绝为它生成链接。因此,第一步始终是保留与用户版本匹配的模块,并从 manifest.json 和 src/index.tsx 中删除另一个模块。
如果应用应该放在其他位置,则要在以下两处同时修改:
manifest.json- 以模块类型为键的modules映射src/index.tsx- 对应的prepare*调用,必须是同步调用,并位于顶层
两处必须相互一致,并与宿主在 context.app.type 中发送的值一致。如果不一致,SDK 就不会调用 prepare(),宿主会显示空白 iframe,而且控制台也不会说明原因。
编辑器模块需要 modes;资源面板需要 fileNamePattern;上下文菜单项需要一个 options 三元组,其合法目标取决于所在位置。如果用户的项目没有文件——也就是基于字符串的项目——请设置 stringBasedAvailable: true,因为默认值是 false;否则,该应用会在用户的所有项目中消失,而且不会在任何地方报错。
references/modules.md 提供了各类型的对照表,列出通俗易懂的放置位置说明、prepare*、额外字段、版本限制,以及 preview 是否能打开该模块。
3. 编写 UI
编写 SDK 代码之前,请阅读 references/sdk.md——其中包含来自可运行脚手架的验证片段,以及类型检查无法发现的问题。有两个问题会直接影响用户:布局变更后要调用 resize(),否则应用看起来会被截断;还要设计真正的空状态,因为首次使用时没有 TM 的组织,或没有术语表的项目,会显示空白屏幕,让用户以为应用坏了。
每个应用都必须支持本地化,这不是可有可无的附加项。 脚手架已经为此做好配置——包括 Lingui、locales/ 文件夹和目录文件——因此默认做法是保留这些内容,删除它们反而是不按默认做法操作。编写时就要用宏(<Trans>、<Plural>、t`)包装所有面向用户显示的字符串,而不是事后再处理:要补上本地化,就得重新修改每一行 JSX。随后,npx crowdin-serverless-apps extract 会将字符串提取到 locales/*.po,而 build 默认会重新提取。这个应用运行在一个本地化平台中,平台用户使用各自的语言;他们最先注意到的就是硬编码的英文。
4. 添加 Logo
脚手架附带一个占位文件 public/logo.svg,如果不替换它,一眼就能看出项目没人收尾——菜单里的每个应用都会显示同一个通用图标。请绘制能体现应用用途的图案:翻译记忆库用图层,报告用条形图,术语表用标签。
使用与占位图相同的正方形 viewBox:0 0 64 64;不要添加文字,因为它的显示尺寸大约只有 24px;为图案设置独立背景,因为宿主主题会在浅色和深色之间切换,而且 CSS 无法作用于 <img> 内部;保持文件自包含,不要引用远程字体或图片;大小控制在几 KB 以内。将文件写入 public/logo.svg——manifest 已经指向 /logo.svg,而 public/ 下的所有内容都会打包。之后确认 dist/logo.svg 存在,并且出现在 dist/bundle.zip 中。
5. 确保检查通过
pnpm typecheck && pnpm lint
npx crowdin-serverless-apps manifest validate
只要这三项中有任何一项未通过,就不要交付代码。typecheck 能发现虚构的 SDK API;它和 lint 都不会读取 manifest.json,这正是 manifest validate 的用途。它会根据架构检查文件,并将 scopes 与代码进行核对:应用通过 API 客户端发出的每个调用,都会与该方法所需的 scope 匹配;createStorage() 会与 application.storage 匹配;然后将结果与清单进行比较。如果清单未涵盖某个调用,就会指出问题并给出需要添加的确切字符串;如果声明的 scope 没有被任何调用使用,则会显示一条提示。验证可离线进行——无需登录,也无需关联应用——但 scope 检查会读取 node_modules 中的包,因此请在 pnpm install 后运行,并在清单或 API 调用发生变化时再次运行。如果发现问题,它会以状态码 1 退出,按章节归类问题,并在每项下方打印一行 fix;请查看这条提示,它会列出可接受的值,包括 scope 字符串。
如果检查通过,但附带 scope check skipped 提示,就说明尚未检查 scope。按照提示操作——运行 pnpm install,或者在早于这项检查的应用中运行 pnpm update @crowdin/serverless-apps-cli @crowdin/crowdin-api-client——然后再次运行(scopes.md)。
通过检查不代表 Crowdin 一定会接受该清单,只代表那些容易避免的错误已经排除。
6. 同步清单
npx crowdin-serverless-apps manifest status # exit code 1 means local and remote differ
npx crowdin-serverless-apps manifest push
请在发布之前同步,而不是发布之后。publish 只会推送构建包;modules、scopes 和 name 只有通过推送才会一并更新。preview 也会读取 Crowdin 上的清单,而不是磁盘上的清单,因此未推送的更改意味着你预览的还是旧位置。
7. 发布,而不是运行开发服务器
npx crowdin-serverless-apps publish -y
构建并上传 dist/bundle.zip,然后切换 Crowdin,让它提供该文件。不要启动 dev:它会将应用绑定到你的机器上,并要求用户保持终端打开,这与交付一个开箱即用的应用背道而驰。只有当用户想在自己的终端中快速本地迭代时,dev 才是合适的工具。请明确说明其中的取舍——每次迭代现在都要重新构建并上传,而不是即时重新加载。
8. 确认可见范围
npx crowdin-serverless-apps list --json
每个应用都会报告其解析后的 permissions,因此请查看实际值,不要想当然。全新安装的权限是 user: owner,这意味着应用只对创建者有效,对其他人不可见;而且由于 create 已在你进行任何修改之前安装了应用,之后再在清单中设置 default_permissions 也不会扩大现有安装的权限。
因此,请根据请求确定受众(“让翻译人员都能看到”对应 all,供应商页面对应 guests,费用报告对应 owner);如果需要的范围比 list --json 报告的更广,就用通俗的语言引导用户前往 Crowdin 应用管理器。如果跳过这一步,即使所有检查都通过,最终也可能出问题:他们分享了链接,同事却什么都看不到,应用看起来就像坏了一样。
请记住,调用以查看者的身份执行。为翻译人员构建的应用,只能依赖翻译人员有权执行的操作,否则恰好会在它服务的对象那里返回 403。
9. 打开并查看
npx crowdin-serverless-apps preview --module <key> [--project <id|identifier>]
始终传入 --module:如果有多个模块且未指定标志,CLI 会拒绝选择。项目模块和 editor-right-panel 需要指定项目——省略 --project 时,会默认使用登录用户有权访问的第一个项目;这对于快速冒烟测试没问题,但只要内容依赖项目,就不适用,因此请传入用户链接中的项目。
18 种类型中有 7 种没有可打开的页面——除了 editor-right-panel 之外的 3 种编辑器类型,以及 modal、chat、context-menu 和 navbar-extension。对于这些类型,preview 会报错,而不是以降级方式运行。这是预期行为,不是 Bug:交接内容会是一条简短的导航说明,而不是链接。在为这类放置位置搭建脚手架之前,请先告知用户,并提供可链接的替代方案。
接着进行验证,因为到目前为止的所有步骤都不能证明应用能正常渲染:typecheck、lint 和 publish 都无法检测空白 iframe、403、被裁切的面板或空白屏幕。如果可以操控浏览器,请打开链接,确认应用正常显示且控制台没有错误。如果不行,请明确说明尚未验证,并请用户打开链接后告知所见内容。绝不要仅凭打印出的 URL 就报告成功。
在消息中将链接作为可点击链接交给用户——preview 会在你的环境中打开浏览器,而该环境可能不是用户的环境。
身份验证握手
登录采用浏览器 OAuth 流程,因此需要用户参与。请在后台启动流程,这样你就能在等待时读取其输出:
npx @crowdin/serverless-apps-cli@latest login
在非交互式 Shell 中,它会以纯文本形式打印授权 URL——open this link (https://accounts.crowdin.com/oauth/authorize?...)。提取该 URL,并以可点击链接的形式交给用户;CLI 自己尝试打开浏览器的操作在你的环境中可能不会生效。流程会等待 5 分钟,然后放弃——请说明这一点;如果用户离开了,请重新启动流程。
绝不要在聊天中索要令牌。如果机器完全没有浏览器,也可以通过环境变量 CROWDIN_PERSONAL_TOKEN 进行身份验证。
遇到问题时
| 你看到的内容 | 含义 |
|---|---|
Not logged in. Run crowdin-serverless-apps login first. | 身份验证握手。 |
执行 create 时 CLI 卡住且没有输出 | 缺少 --template 或 --yes;CLI 正在等待你选择。 |
This app declares 2 modules. Pass --module <key> | 尚未修改的脚手架。删除此版本中不再使用的模块,然后传入对应的键。 |
Module type X only has a page on Crowdin Enterprise / only on crowdin.com | 此放置位置受版本限制。请选择其版本中存在的位置。 |
Module type X has no page of its own | 七种无法打开的类型之一。预期行为是交接导航,而不是链接。 |
| iframe 空白,或主机报告应用未启动 | 清单类型、prepare* 和 context.app.type 不一致;或者注册项不在顶层;或者同一类型的两个模块没有键。 |
| 应用已发布,但未出现在 UI 插槽中 | manifest push 已跳过,或这是一个缺少 stringBasedAvailable 的基于字符串的项目。 |
| 应用已打开,但什么也没显示 | 数据为空,或者回退到的首个项目中没有术语表、TM 或字符串。先检查这些,再怪代码。 |
运行时出现 403 Endpoint isn't allowed for token scopes. | 缺少清单作用域。运行 manifest validate;它会指出调用位置以及需要添加的字符串。如果验证通过,运行 manifest status——作用域已写入磁盘,但尚未推送。如果显示 scope check skipped,请先按说明处理;显示该提示的验证通过结果并未检查作用域(scopes.md)。 |
| 其他任何 403,或列表为空,但所有检查均通过 | 这是查看者本人的权限问题,而不是清单问题:调用以打开应用者的身份运行,任何检查都无法发现这一点。请根据此人可能执行的操作来构建,或缩小受众范围。 |
| 所有存储调用均被拒绝 | 清单的 scopes 中缺少 application.storage——manifest validate 会指出需要此权限的 createStorage() 调用;或者验证通过,但清单更改尚未推送。 |
kv.user.* 报错,但共享键可用 | 查看者是匿名用户——没有用户身份。在提供按用户区分的功能前,请先检查上下文中的 user.id。 |
对 module:...: 下的键执行写入时被拒绝 | 该键指定的模块不是应用当前运行时所用的模块,或该模块未知。读取时同样的情况不会报错:get 会返回 undefined,列表会跳过这些记录。 |
You have no accessible projects to preview this module in. | 要么没有项目访问权限,要么这是需要公开项目的 crowdsource 模块。 |
Could not list your projects - this login may lack the project scope. | 重新登录以授予该权限。 |
GraphQL is not available to serverless apps | 代理仅支持 REST /api/v2。 |
FormData bodies are not supported through AP.apiRequest | 请传入原始 Blob、File 或 ArrayBuffer。 |
Crowdin 拒绝了 manifest push | 运行 manifest validate;它会指出有问题的路径。 |
npx 因网络或权限问题而失败 | 这是环境问题,不是 CLI 问题。请让用户授予权限。 |
参考资料
- references/modules.md - 所有模块类型:放置位置、
prepare*、清单字段、版本限制,以及preview可打开的内容 - references/sdk.md - 已验证的 SDK 代码片段,以及类型检查无法捕获的问题
- references/scopes.md -
manifest validate如何从代码推导作用域,以及它无法检查的内容 - references/cli.md - 命令和标志参考、环境变量,以及每条命令会写入的内容