Vite X Figma 打造设计师专属的 i18n 插件

1,849 阅读5分钟

动机

有一位同学发现我在研究 Figma 插件,故问我若给设计师提供一个i18n插件,用于提前预览多语言环境下的设计稿是否可行。正好我之前想做的某个 Figma 插件实现起来比较复杂,而这个i18n需求实现起来又比较简单,正好可以用来练手和踩坑。源码地址为github.com/ascodelife/…

i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。

使用 Vite 配置 Figma 插件开发与生产环境

Figma 插件由两部分构成,分别为:

  1. 可以直接访问 Figma document 并运行在主线程的代码,它的运行环境由 Realms sandbox 提供。

  2. 可以直接访问浏览器 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协议,会受到跨域的限制。

有图有真相。

image-20220420225339344.png

image-20220420225355067.png 那么这时候很容易想到的解决方案有两种,

  1. 所有脚本都默认禁用type=module的引用方式,还需要修正一下引入路径,使用相对路径替换。(目前在vite生态中没找到好的解决方案,欢迎讨论)
  2. 将所有脚本都内联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.jsui字段指向打包后的index.html文件。editorType字段是一个数组,表明该插件适用的编辑器,由于该插件可以同时兼容 Figma 和 Figjam,因此此处值为[ "figjam", "figma" ]

Figjam 是由 Figma 团队新推出的一款在线多人协作的白板工具。

插件设计与实现

大部分i18n插件都有相似的实现思路,即:

  1. 本地上传多语言环境的配置文件或直接通过API对接翻译平台。
  2. 将待翻译的主体解析为可遍历的对象,并遍历对象中的所有文本。
  3. 匹配翻译结果,并修改原对象的文本。
  4. 生成翻译报告。

具体流程

类似的,我们可以得到在 Figma 平台下i18n插件的具体流程。

流程图.drawio.png

其中配置文件格式定义为 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": "つくえ"
  }
}

部分实现细节

  1. 减少遍历节点,一个设计图上会存在很多节点,但是我们只关心文本节点,此时可以使用官方提供的API进行优化,提前指定节点类型为TEXT,具体的优化细节由 Figma 提供,不需要开发者关心。
const nodes = figma.root.findAllWithCriteria({
	types: ["TEXT"],
});
  1. 文本替换,在替换文本时,需要先异步加载所需字体。此处使用了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/…

example.gif

参考文章

  1. how we build the figma plugin system
  2. ECMAScript spec proposal for ShadowRealm API
  3. vite 构建生产版本库模式
  4. vite-plugin-singlefile
  5. figma plugin 开发文档