前言
"每次看到设计师在Figma里复制粘贴文案,我就想写个工具帮他们..."
现实中的开发困境
想象一下这个场景:你是一个前端开发工程师,设计师给你发来一个Figma设计稿,里面有几十个页面,每个页面都有大量的文案内容。而你的业务是面对国际化的,需要生成一个 原始的 JSON 文案去找翻译的同学来帮你翻译成各个国家的语言🤯并且这个 JSON 要根据你具体的项目来调整格式,很好,现在你需要做的任务如下:
- 手动复制文案 - 在Figma中一个个点击文本,复制粘贴到代码里
- 整理成JSON格式 - 手动整理成前端可用的多语言文件格式
- 翻译成多种语言 - 还要找翻译工具逐个翻译
- 保持数据结构一致 - 确保所有语言版本的键值对应正确
这个过程不仅效率低下,还容易出错。更要命的是,当设计师修改了文案,你又要重复一遍这个痛苦的过程...这无疑是很不高效的行为
那么这时候,作为一个有"懒癌"的程序员,我开始思考如何提高开发效率:
- 能否自动从Figma提取文案?
- 能否直接调用AI进行智能分析和翻译?
- 能否一键生成前端需要的多语言JSON文件?
于是,这个React Figma AI Chrome扩展项目就诞生了。
💡 从痛点到解决方案
作为一个经常被奇奇怪怪的需求"折磨"的开发者,我深知一个道理:好的工具都是从解决真实痛点开始的。现在让我们把刚才提到的痛点重新梳理一下,但这次我们要从"程序员思维"的角度来分析:
需求1:自动化文案提取
- 痛点:手动复制粘贴,效率低下,特别是我开发的时候,有时候面对一些游戏规则页(比如一整页的文字说明搭配几个表格的那种),光是复制+整理成完整的 JSON 就已经足够痛苦了,基本上一个内容多的说明页,去创建一个符合规范的 JSON 就需要十分钟左右了😭
- 解决思路:通过Chrome扩展直接访问Figma API,自动提取页面文案,这样就节省掉我们复制粘贴的时间了
需求2:智能文案处理
- 痛点:文案需要结构化整理,还要翻译,像往常我开发的时候,除了自己手动去整理以外,最常用的方式就是让 AI 去帮我整理成一个可以开发的JSON,但往往需要手动去输入一大堆的Prompt ,才可以生成一个勉强够用的,或者你会说:"哥们!你弄到备忘录里,需要的时候再弄不就成了吗?",那我问你,这样优雅吗?每次都需要从备忘录里找到Prompt,然后调整项目的描述 or 项目需要的 JSON 格式,这也太繁琐了吧...
- 解决思路:接入AI服务,让AI帮我们干"脏活累活",最好可以留出调整项目描述和JSON 格式的区域
需求3:一键生成多语言JSON
- 痛点:手动整理JSON格式,容易出错
- 解决思路:让AI直接输出标准化的JSON格式,并且支持多语言翻译
那么现在我们的开发思路就已经定好了:

开发流程
初始化项目
bash
mkdir figma-analyzer-extension
cd figma-analyzer-extension
npm init -y
开发技术栈选择方面,因为我先前一直是写 Vue 的,对 React 始终保持着好奇,但因为工作原因,一直没有机会去用到这个传奇的前端库,所以这次自己的小项目就选择了 React 来进行开发了,至于打包工具方面,就选择我们熟悉的 Vite 来进行打包构建就好~
除了这些主要的技术栈,我们还要根据我们的项目需求来选择一些有趣又有用的库,belike:
markdown
- `@crxjs/vite-plugin`:专为Chrome扩展优化的Vite插件(**开发 chrome extension 的神器!**)
- `@types/chrome`:Chrome扩展API的TypeScript类型定义
- `react-json-pretty`:用来美化显示JSON结果,方便我们直接在插件里浏览 AI 生成的JSON,这个JSON-pretty 足够**轻量美观**
中间的一些细节就省略掉了,如果感兴趣的话可以查看developer.chrome.com/docs/extens... 官方文档,基本上和我们的也大差不多,在稍作调整后,我们的这个项目结构如下
markdown
figma-analyzer-extension/
├── src/
│ ├── components/ # React组件
│ │ └── FigmaAnalyzer.tsx
│ ├── manifest.json # Chrome扩展配置文件(重要!)
│ ├── popup.html # 扩展弹窗的HTML
│ ├── popup.tsx # React应用入口
│ ├── popup.css # 样式文件
│ ├── background.ts # Service Worker(后台脚本)
│ ├── types.ts # TypeScript类型定义
│ ├── constants.ts # 常量定义
│ ├── prompts.ts # AI提示词模板
│ ├── figmaApi.ts # Figma API服务
│ └── vite-env.d.ts # Vite环境类型定义
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── README.md
让我稍微做一下解释,这里之所以要prompt 单独放在一个 ts 文件,是因为我们需要根据不同的功能来拆分不同的prompt,并且需要根据用户的具体使用场景,来调整 prompt,如果放在 React 组件中,会让整个项目看上去非常的臃肿,所以这里单独进行拆分了
接入大模型之我全都要
wait?!我们是不是忘记了一个很重要的事情?选择什么大模型呢?如果太贵了会不会得不偿失呢?市面上的大模型也太多了吧...选择困难症了🤡
jsx
// 程序员的内心独白
const aiServices = {
openai: { price: '💰💰💰', quality: '🌟🌟🌟🌟🌟', speed: '🚀🚀🚀' },
claude: { price: '💰💰', quality: '🌟🌟🌟🌟🌟', speed: '🚀🚀' },
deepseek: { price: '💰', quality: '🌟🌟🌟🌟', speed: '🚀🚀🚀' },
ollama: { price: '🆓', quality: '🌟🌟🌟', speed: '🐌' }
};
// 最后决定:小孩才做选择,成年人表示,我全都要!
const solution = '让用户自己选择,我们都支持';

那我们来简单实现一下 AI调用模块:
jsx
// 支持多种AI服务的统一接口
const callAI = async (prompt, provider) => {
switch(provider) {
case 'openai': return await callOpenAI(prompt);
case 'deepseek': return await callDeepSeek(prompt);
// ... 其他服务
}
};
接着让我们实现一下接入 AI 大模型,这里选择性价比最高的 deepseek 来作为示例(其他的大模型同理):
jsx
function buildPrompt(request: AIAnalysisRequest): string {
const { operation, figmaData, projectDescription, targetLanguage } = request;
if (operation === 'translate') {
// 纯翻译模式
const textsToTranslate = figmaData.texts.map(t => t.text).join('\n');
const targetLang = getLanguageName(targetLanguage || 'en');
const additionalInstruction = `\n\n**最终提醒**:以上共 ${figmaData.texts.length} 行文案,每行输出格式必须是:英文原文:${targetLang}译文`;
return TRANSLATION_PROMPT_TEMPLATE
.replace(/\{targetLanguage\}/g, targetLang)
.replace('{textsToTranslate}', textsToTranslate) + additionalInstruction;
} else if (operation === 'translate-and-structure') {
// 翻译+结构化模式
const allTextsFormatted = figmaData.texts.map((text, index) =>
`${index + 1}. "${text.text}"`
).join('\n');
const projectDesc = projectDescription || '网页界面设计项目';
const targetLang = getLanguageName(targetLanguage || 'en');
const strictReminder = `\n\n**再次强调**:请确保JSON中只包含上述 ${figmaData.totalTextCount} 条提取文案的翻译版本,不要添加任何额外内容!`;
return TRANSLATE_AND_STRUCTURE_PROMPT_TEMPLATE
.replace(/\{targetLanguage\}/g, targetLang)
.replace('{textCount}', figmaData.totalTextCount.toString())
.replace('{allTexts}', allTextsFormatted)
.replace('{projectDescription}', projectDesc) + strictReminder;
} else {
// 结构化JSON生成模式
const allTextsFormatted = figmaData.texts.map(text => text.text).join('\n');
const projectDesc = projectDescription || '网页界面设计项目';
const strictReminder = `\n\n**再次强调**:请确保JSON中只包含上述 ${figmaData.totalTextCount} 条提取的文案,不要添加任何额外内容!`;
return ANALYSIS_PROMPT_TEMPLATE
.replace('{textCount}', figmaData.totalTextCount.toString())
.replace('{allTexts}', allTextsFormatted)
.replace('{projectDescription}', projectDesc) + strictReminder;
}
}
// DeepSeek API调用实现
async function callDeepSeekAPI(prompt: string, apiKey: string): Promise<string> {
const requestBody = {
model: 'deepseek-chat',
messages: [{ role: 'user', content: prompt }],
temperature: 0.2, // 降低温度提高一致性
max_tokens: 2000
};
console.log('🚀 发送到DeepSeek的请求:', requestBody);
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text();
console.error('DeepSeek API Error:', errorText);
throw new Error(`DeepSeek API错误: ${response.status}`);
}
const data = await response.json();
const content = data.choices[0]?.message?.content;
if (!content) {
throw new Error('DeepSeek API返回空内容');
}
return content;
}
接入 Ollama 时,遇见阻碍
当我兴高采烈的接入一个又一个主流大模型的时候,也同时考虑到了这个免费的工具,不仅可以本地调用大模型,还可以保证信息的隐私,来处理一些敏感需求的话,ollama 再合适不过了,但在接入的时候,遇见了第一个大坑:Error 403
在查阅社区的 issues 的时候,我发现了有不少开发者也遇见了同样的问题:github.com/ollama/olla...
bash
macOS上:
launchctl setenv OLLAMA_ORIGINS "*"
才刚解决完这个报错,又发现我下载的 deepseek-r1 8b 模型,一直会返回 think 部分:并且这时候,我们的解析接口返回也失效了:

同样的在社区找到了相同的疑问:github.com/deepseek-ai... 官方也并没有在API 文档中说相关的内容...搜索了半天也没有找到结果,于是我尝试去了解 AI 的相关概念,比如
- stream(流式输出)
- temperature (控制生成文本随机性的重要参数)
- think (深度思考)
哦!找到了,在 ollama.com/blog/thinki...
手动将think 设置成 false 即可!顺带一提,我个人不是很喜欢流式输出,即使现在很多的对话式 AI(如 ChatGPT 或者 DeepSeek)都选择了流式输出,但我们还是要根据自己的开发项目来设置,在我们这个需求中,直接获取到最终的结果就行,不需要关注生成的过程

Figma数据获取 - 从注入脚本到REST API的重构之路
最开始我思考的获取 Figma 文本的方式是注入脚本,通过在 Figma 页面中注入 JavaScript 代码来获取选中元素的数据
jsx
// 早期的注入脚本方案(已弃用)
function getSelectedElements() {
// 直接访问Figma的内部API
const selection = figma.currentPage.selection;
return selection.map(node => ({
id: node.id,
name: node.name,
text: node.characters
}));
}
最开始的时候我还沾沾自喜,认为自己的这个实现思路很完美,后面在获取元素的时候,发现经常出现"无法获取选中元素"的错误,这对用户的体验无疑是很差的,这时候,我想到了直接使用Figma API:
jsx
export class FigmaApiService {
private apiToken: string;
private baseUrl = 'https://api.figma.com/v1';
constructor(apiToken: string) {
this.apiToken = apiToken;
}
// 获取Figma文件数据
async getFile(fileId: string): Promise<FigmaFileResponse> {
const response = await fetch(`${this.baseUrl}/files/${fileId}`, {
headers: {
'X-Figma-Token': this.apiToken,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Figma API 错误 (${response.status}): ${errorText}`);
}
return await response.json() as FigmaFileResponse;
}
}
// 从Figma URL中提取文件ID
static extractFileIdFromUrl(url: string): string | null {
const patterns = [
// 匹配 /file/ 或 /design/ 路径
/(?:www\.)?figma\.com\/(?:file|design)\/([a-zA-Z0-9-_]+)/,
// 备用模式:更宽松的匹配
/figma\.com\/[^/]+\/([a-zA-Z0-9-_]+)/
];
for (const pattern of patterns) {
const match = url.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
}
// 从URL中提取节点ID(当用户选中元素时)
static extractNodeIdFromUrl(url: string): string | null {
const nodeIdMatch = url.match(/[?&]node-id=([^&]+)/);
if (nodeIdMatch) {
let nodeId = decodeURIComponent(nodeIdMatch[1]);
nodeId = nodeId.replace('%3A', ':').replace('-', ':');
return nodeId;
}
return null;
}
// 递归提取节点中的所有文案
private extractTextsFromNode(node: FigmaNode, texts: FigmaTextInfo[] = []): FigmaTextInfo[] {
try {
// 如果是文本节点且有文案内容
if (node.type === 'TEXT' && node.characters) {
const boundingBox = node.absoluteBoundingBox || { x: 0, y: 0, width: 0, height: 0 };
texts.push({
id: node.id,
name: node.name,
text: node.characters,
fontSize: node.style?.fontSize || 16,
fontFamily: node.style?.fontFamily || 'Unknown',
x: boundingBox.x,
y: boundingBox.y,
width: boundingBox.width,
height: boundingBox.height
});
}
// 递归处理子节点
if (node.children && node.children.length > 0) {
for (const child of node.children) {
this.extractTextsFromNode(child, texts);
}
}
} catch (error) {
console.warn('提取节点文案时出错:', error, node);
}
return texts;
}
注意,我们要使用 Figma API 的话,需要使用 Figma API token,这里我们给一个链接,方便用户点击后直接跳转去获取 Figma API Tokenhttps://www.figma.com/developers/api#access-tokens

AI Prompt工程 - 让AI理解你的需求
第一版Prompt的失败经历
最初的Prompt设计得过于简单:
javascript
请分析以下文案并生成JSON格式的结果:
{文案内容}
结果AI经常返回格式不规范的内容,有时候还会添加额外的说明文字,即使我将temperature设置的足够低,也有很多奇怪的生成,导致JSON解析失败。
这里补充说明一下:
在 AI(尤其是语言模型)中,"temperature"(温度)是一个控制生成文本随机性的重要参数。
🎲 什么是 Temperature?
每次模型生成下一个 token(词或子词)时,会基于一组 logits (原始分数)通过 Softmax 函数将其转为概率分布。Temperature TTT 会影响 Softmax 的平滑程度: 低温度(T < 1) :概率分布更陡峭,模型更倾向于选择最高概率的词,也就是最"保守""确定"的输出 高温度(T > 1):概率分布更平坦,增加了选择概率较低词的机会,生成更"多样""有创造性"的文本
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> s o f t m a x ( z i / T ) softmax(zi/T) </math>softmax(zi/T)
举个对比例子
在某一句话生成中,如果 logits 是 [2,1,−1][2,1,-1][2,1,−1],Softmax 转换后会是大约 [0.67,0.25,0.08][0.67, 0.25, 0.08][0.67,0.25,0.08]。但如果引入不同Temperature:
- T → 0:几乎总是选第一个 token,输出高度重复、确定。在日常中,对于技术写作,翻译或者说问答之类的就可以选择这种Temperature
- T = 1:保留原始分布。
- T > 1 :分布变平,次优的词也有机会被采样,比如"旁门左道"出现的可能性更高,如果你有天马行空的想法,并且不是很在意会不会出错的话,就可以选择这种
这里关于Temperature的科普我们就讲到这里吧,如果对这个感兴趣的话,可以看 Medium 上的这篇文章,讲的非常详细:medium.com/%40amansing...
回到我们的项目吧,经过大量测试和优化,最终的Prompt模板是这样的:
tsx
export const ANALYSIS_PROMPT_TEMPLATE = `
你是一个专业的UI/UX文案分析师,请分析以下 {textCount} 条从设计稿中提取的文案内容。
项目描述:{projectDescription}
需要分析的文案:
{allTexts}
请严格按照以下要求输出JSON格式的分析结果:
1. 必须是有效的JSON格式,不要包含任何其他文字说明
2. 只分析上述提取的 {textCount} 条文案,不要添加额外内容
3. 结构化输出,包含页面标题、描述、建议等字段
输出格式示例:
{
"__page_title": "页面标题",
"button_text": "按钮文案",
"description": "描述文案",
"title": "主标题"
}
**再次强调**:请确保JSON中只包含上述 {textCount} 条提取的文案,不要添加任何额外内容!
`;
注意!这里只是针对于我的项目来写的内容,如果是你来做的话,可以稍微调整 JSON 格式,也算是一劳永逸的事情了
效果展示
让我们看看最终的页面展示效果,用 Figma 官方的插件入门开发来作为演示:



最后附上一个项目结构图和 github 地址:github.com/isolcat/fig...
如果项目可以帮助到你,欢迎点 star~