mcpField
C
create-app

create-app

github.com/crowdin开发949 次安装1 周前更新
代码仓库

关于 create-app

create-app 是一项用于指导构建 Crowdin 应用的技能,面向翻译人员和本地化管理者的需求。它引导 Agent 查阅权威文档、厘清应用放置位置与可见范围,并按步骤完成构建和交付。

主要功能

  1. 01引导以非技术语言澄清需求
  2. 02指导检查 Crowdin 文档与平台限制
  3. 03规划应用位置、逻辑和可见范围
  4. 04说明前端应用的数据存储边界
  5. 05提供从预检到交付的构建流程

适用场景

  1. 01为术语查询需求规划 Crowdin 应用
  2. 02将手动整理的本地化数据表改为应用界面
  3. 03根据 Crowdin 页面链接确定版本和项目
  4. 04判断定时邮件或外部调用需求的可行替代方案

安装

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

$npx skills add https://github.com/crowdin/skills --skill create-app

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
cli.md
modules.md
scopes.md
sdk.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.mdCrowdin Storage——持久化的共享记录、用户级记录和模块范围记录
building-app/context.md上下文、主题、事件
building-app/host-actions.md编辑器和宿主操作
building-app/user-interface.mdUI 套件
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”。与问题列表相比,陈述列表更容易让人指出异议。

仅在以下情况下提问:

  1. 无法从请求中推断出展示位置。 “在编辑器中字符串旁边”已经足够明确。“在项目中”则存在实际选择:Tools 部分还是 Reports 部分——应说明这两个位置,并给出建议后再询问。
  2. 两种实现会让用户看到不同内容。 不要问“应该怎么做”,而要问“进度应该按语言显示为图表、按文件显示为表格,还是只显示一个数字?”。
  3. 平台迫使他们在不同方案之间取舍。 见下文。

一次只问一个问题,并附上你的建议答案,这样回复“是”就足够了。如果他们不知道,就采用你的建议并继续。一个问题都不问,也完全没问题。

然后展示说明并等待对方同意: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 - 命令和标志参考、环境变量,以及每条命令会写入的内容

评论

支持 Markdown