动机
有一位同学发现我在研究 Figma 插件,故问我若给设计师提供一个i18n
插件,用于提前预览多语言环境下的设计稿是否可行。正好我之前想做的某个 Figma 插件实现起来比较复杂,而这个i18n
需求实现起来又比较简单,正好可以用来练手和踩坑。源码地址为github.com/ascodelife/…
i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。
使用 Vite 配置 Figma 插件开发与生产环境
Figma 插件由两部分构成,分别为:
-
可以直接访问 Figma document 并运行在主线程的代码,它的运行环境由 Realms sandbox 提供。
-
可以直接访问浏览器 API 的用于插件 UI 构建的代码,它运行在 iframe 中。
这两部分组件分别承担了不同的职能,前者可以通过 Figma 暴露的 API 来拿到和修改设计稿的信息,后者则主要用于交互,它们之间通过与postmessage
相同的机制来进行通信。具体设计思路可以参照这篇文章: how we build the figma plugin system
当作为插件引入时,若你需要用户交互界面,则需要对两者都进行打包,打包的细节略有不同。
主线程
官方默认给的插件模板是使用了tsc
进行打包,但实际开发中发现,tsc
打包出来的产物引入时经常会报错,因此我将它略加修改后改为使用vite
来进行打包。这需要我们把它当成库文件来打包,详情可以参照官方文档:vite 构建生产版本库模式。打包后可以得到两种产物,一种是es module
一种是umd
,由于插件支持es module
,所以只需要导入es module
即可。
插件 UI
同样的,UI 也使用 vite 来打包,使用vite
默认的react
模板即可,但是打包过程中还存在一些坑。
由于vite
打包出来后的js
文件是通过type=module
的形式来引入的,在开发模式下由于起了http
服务因此不受跨域限制,但是插件要求加载的是离线网页,离线网页下js
文件走的是file
协议,会受到跨域的限制。
有图有真相。
那么这时候很容易想到的解决方案有两种,
- 所有脚本都默认禁用
type=module
的引用方式,还需要修正一下引入路径,使用相对路径替换。(目前在vite
生态中没找到好的解决方案,欢迎讨论) - 将所有脚本都内联到
index.html
中,其中用到的插件为vite-plugin-singlefile。在离线环境中,我们不要考虑缓存和体积的问题,因此内联对性能而言没有任何影响。
根据方案2,可以得到这样一个打包产物index.html
,即All in index.html
。
<html>
xxxxxx
<script type="module">
//assets/index.be34fa62.js
xxxxx
// react.production.min.js
xxxx
.......
</script>
</html>
插件配置文件
Figma 插件使用manifest.json
来描述插件的信息,我们主要关注三个字段,其中main
指向主线程代码的入口,那么它应该对应vite
通过库模式打包后的产物,即xxx.es.js
。ui
字段指向打包后的index.html
文件。editorType
字段是一个数组,表明该插件适用的编辑器,由于该插件可以同时兼容 Figma 和 Figjam,因此此处值为[ "figjam", "figma" ]
。
Figjam 是由 Figma 团队新推出的一款在线多人协作的白板工具。
插件设计与实现
大部分i18n
插件都有相似的实现思路,即:
- 本地上传多语言环境的配置文件或直接通过
API
对接翻译平台。 - 将待翻译的主体解析为可遍历的对象,并遍历对象中的所有文本。
- 匹配翻译结果,并修改原对象的文本。
- 生成翻译报告。
具体流程
类似的,我们可以得到在 Figma 平台下i18n
插件的具体流程。
其中配置文件格式定义为 json 文件,具体为
{
"zh-cn": {
"id-1": "实验",
"id-2": "事实",
"id-3": "洞察力",
"id-4": "机遇"
},
"en-us": {
"id-1": "Experiments",
"id-2": "Facts",
"id-3": "Insights",
"id-4": "Opportunties"
},
"ja-jp": {
"id-1": "じっけん",
"id-2": "じじつ",
"id-3": "どーさつ",
"id-4": "つくえ"
}
}
部分实现细节
- 减少遍历节点,一个设计图上会存在很多节点,但是我们只关心文本节点,此时可以使用官方提供的
API
进行优化,提前指定节点类型为TEXT
,具体的优化细节由 Figma 提供,不需要开发者关心。
const nodes = figma.root.findAllWithCriteria({
types: ["TEXT"],
});
- 文本替换,在替换文本时,需要先异步加载所需字体。此处使用了
Promise.all
来做字体的并发加载。
// matchedNodes 是匹配到的可翻译的所有文本节点
Promise.all(
matchedNodes.map(async ({ node, newChars }) => {
// 1. 获取文本节点的字体和样式
const { family, style } = node.fontName as FontName;
// 2. 异步加载所需字体和样式
await figma.loadFontAsync({ family, style });
// 3. 修改节点文字
node.characters = newChars!;
})
);
实现效果展示
此处使用 FigJam 平台下生成的draft template
来展示。源码地址为github.com/ascodelife/…