news 2026/9/16 21:28:05

Builder.io Svelte SDK 实战:为 SvelteKit 应用构建多语言(本地化)页面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Builder.io Svelte SDK 实战:为 SvelteKit 应用构建多语言(本地化)页面

Builder.io Svelte SDK 实战:为 SvelteKit 应用构建多语言(本地化)页面

【免费下载链接】builderVisual Development for React, Vue, Svelte, Qwik, and more项目地址: https://gitcode.com/GitHub_Trending/bu/builder

本文以仓库中examples/svelte/localized-sveltekit示例为主线,讲解如何在 SvelteKit 中集成 Builder.io 官方 Svelte SDK,实现"基于 Builder 可视化编辑器维护内容、按语言前缀(/en/fr/de)分发页面、并渲染本地化自定义组件"的完整闭环。读完本文,你将掌握 Builder.io 后台配置、SvelteKit 多语言路由与 301 重定向、服务端内容抓取(fetchOneEntry)以及Content组件渲染本地化内容的标准做法。

一、示例概览:它解决了什么问题

localized-sveltekit是 Builder.io 官方仓库中演示"Svelte SDK × SvelteKit × 本地化"三件事如何协作的最小可运行项目。它的核心诉求是:

  • 一个 Builder.io 空间(space)内配置了enfrde三种语言;
  • 站点通过 URL 前缀(如http://localhost:3000/fr/...)区分语言;
  • 页面内容由 Builder 可视化编辑器维护,SvelteKit 负责抓取并按语言渲染;
  • 自定义组件(Counter)中的部分输入字段声明为localized,实现"同一组件在不同语言下展示不同文案"。

整个示例的目录结构如下(详见 examples/svelte/localized-sveltekit):

src/ ├── apiKey.js # Builder 公钥配置 ├── app.html # SvelteKit 应用外壳(HTML 模板) ├── hooks.server.js # 服务端钩子:语言解析 + 301 重定向 + html lang 属性 ├── utils.js # 语言解析工具函数 ├── lib/ │ └── Counter.svelte # 演示用的自定义组件(含本地化输入) └── routes/ └── [lang]/ └── [...catchall]/ ├── +page.server.js # 服务端加载:按语言抓取 Builder 内容 └── +page.svelte # 页面组件:渲染 Builder Content 与自定义组件

其中路由采用 SvelteKit 的动态段([lang])加 catchall([...catchall])的嵌套写法,lang即语言前缀,catchall承接任意子路径——这套结构正是多语言 Builder 页面的骨架。

二、Builder.io 后台设置

在运行代码之前,需要先在 Builder.io 平台完成以下准备(对应原文档 README.md 的 Builder.io Setup 一节):

  1. 登录 builder.io 账号;
  2. 进入账号页面,复制你的 API Key(Public API Key),粘贴到 src/apiKey.js 中导出:
    // TODO: enter your public API key export const BUILDER_PUBLIC_API_KEY = 'f1a790f8c3204b3b8c5c1795aeac4660'; // ggignore

    注意:原 README 写作BUILDER_API_KEY,而当前示例源码中实际导出名是BUILDER_PUBLIC_API_KEY,请以源码为准。文件末尾的ggignore注释表示该示例密钥会被 gitignore 忽略,实际部署时务必替换为你自己的密钥;

  3. 打开 Builder.io 针对名为page的模型(model)的 Visual Editor(可视化编辑器);
  4. 在 Builder 预览区右上角的 URL 输入框中填入http://localhost:3000
  5. 在 Layers(图层)面板中拖入一个组件,它就会实时出现在编辑器中——这就是 Builder 可视化开发的入口。

后台还有一个关键前提:在 Builder 空间中配置好与代码一致的语言列表。示例代码中supportedLocales = ['en', 'fr', 'de'](见 src/utils.js),注释明确写着"Match this with the locales defined in your builder space",即代码里声明的语言集合必须与 Builder 空间里配置的语言一一对应,否则本地化内容无法正确抓取。

三、安装与本地开发

示例使用 Vite + SvelteKit 作为构建工具链。先安装依赖,再启动开发服务器(命令与原文档 Build Setup 一节一致):

# 安装依赖 $ npm install # 以热重载方式在 localhost:3000 启动 $ npm run dev # 或启动服务器并在新浏览器标签页中打开应用 $ npm run dev -- --open

从 package.json 可以看到脚本定义:dev对应vite devbuild对应vite buildpreview对应vite preview。核心依赖是@builder.io/sdk-svelte: ^1.0.27(即仓库 packages/sdks/output/svelte 下的 Svelte SDK 产物),其余为 SvelteKit 开发依赖。

生产构建与预览:

# 生成生产版本 npm run build # 预览生产构建结果 npm run preview

如果要部署到目标环境,可能需要按 SvelteKit 的适配器(adapter)机制为对应平台安装适配器。当前 svelte.config.js 使用的是自动适配器@sveltejs/adapter-auto

关于 vite.config.js 的一个关键细节

src 同级目录的 vite.config.js 中有一处与 SDK 强相关的配置:

optimizeDeps: { /** * `isolated-vm` is an SDK dependency that must be excluded from the * pre-bundling optimization because it cannot (and shouldn't) be bundled at all. */ exclude: ['isolated-vm'] }

isolated-vm是 Svelte SDK 的底层依赖(用于隔离执行环境),它无法也不应被 Vite 预打包,因此必须通过optimizeDeps.exclude排除。这是让本示例正常跑起来的关键配置,其他 SvelteKit 项目中引入该 SDK 时同样需要保留此项,否则可能触发预打包相关的构建错误。

四、多语言路由架构:[lang]+[...catchall]

本地化的实现基础是 SvelteKit 的动态路由。目录src/routes/[lang]/[...catchall]/同时使用了两类动态段:

  • [lang]:匹配语言前缀,如/en/fr/de
  • [...catchall]:rest 参数,匹配语言前缀之后的任意剩余路径,例如/en/blog/post-1中的blog/post-1

这样,任何以语言前缀开头的 URL 都会命中同一个页面处理逻辑,由服务端代码负责"按语言抓取内容"和"决定是否渲染 404"。原 README 指出用户也可以把多个语言映射到同一页面(见下一节+page.server.js中"remove locale from the path to match multiple locales in same page if needed"的注释)。

语言工具函数:utils.js

src/utils.js 集中了语言解析的纯函数,是整个本地化逻辑的基础:

export const getLocaleFromPathname = (pathname) => `${pathname.match(/[^/]+?(?=\/|$)/)}`.toLowerCase(); export const defaultLocale = 'en'; // Match this with the locales defined in your builder space export const supportedLocales = ['en', 'fr', 'de']; export const routeRegex = new RegExp(/^\/[^.]*([?#].*)?$/); // checks if a string is a route request (i.e not an asset request like /image.png) https://regexr.com/73ccb export const isRoute = (pathname) => routeRegex.test(pathname);

四个导出的职责分别是:

导出作用说明
getLocaleFromPathname从路径中提取第一个段作为语言例如/en/abouten/dede
defaultLocale默认语言当前为en,在用户语言无法匹配时兜底
supportedLocales支持的语言白名单当前为['en', 'fr', 'de'],须与 Builder 空间配置一致
isRoute判断是否为页面路由请求用正则^\/[^.]*([?#].*)?$排除图片等静态资源请求(如/image.png),避免把资源请求误当页面处理

五、服务端钩子:语言判定与 301 重定向

本地化场景下,"用户访问无语言前缀的 URL 时应如何处理"是一个必须解决的问题。示例通过 SvelteKit 的 handle 钩子在 src/hooks.server.js 中解决,其流程为:

import { getLocaleFromPathname, defaultLocale, supportedLocales, isRoute } from './utils'; /** @type {import('@sveltejs/kit').Handle} */ export const handle = async ({ event, resolve }) => { const { url, request } = event; const { pathname } = url; // If this request is a route request if (isRoute(pathname)) { // Try to get locale from `pathname`. let locale = supportedLocales.find( (l) => `${l}`.toLowerCase() === getLocaleFromPathname(pathname) ); // If route locale is not supported if (!locale) { // Get user preferred locale locale = `${`${request.headers.get('accept-language')}`.match( /[a-zA-Z]+?(?=-|_|,|;)/ )}`.toLowerCase(); // Set default locale if user preferred locale does not match if (!supportedLocales.includes(locale)) locale = defaultLocale; // 301 redirect return new Response(undefined, { headers: { location: `/${locale}${pathname}${event.url.search}` }, status: 301 }); } // Add html `lang` attribute return resolve(event, { transformPageChunk: ({ html }) => html.replace(/<html.*>/, `<html lang="${locale}">`) }); } return resolve(event); };

整个钩子分三步:

  1. 从路径解析语言:用supportedLocales.find(...)判断当前路径前缀是否属于已支持语言;
  2. 不支持则回退 + 301 重定向:若前缀不在白名单中,则读取请求头accept-language(正则/[a-zA-Z]+?(?=-|_|,|;)/提取主语言标记)作为用户偏好;若仍不匹配支持列表,回退到defaultLocaleen);最终以301状态码返回location: /${locale}${pathname}${search},把用户永久重定向到带语言前缀的 URL。这一步对 SEO 友好——搜索引擎会把无前缀 URL 视为旧地址并跟随跳转;
  3. 注入lang属性:通过transformPageChunk<html>标签改写为<html lang="${locale}">,让浏览器与搜索引擎明确感知页面语言,这是本地化页面的标准做法之一。

六、服务端加载:用fetchOneEntry按语言抓取内容

页面数据在 src/routes/[lang]/[...catchall]/+page.server.js 中通过 SvelteKit 的load函数在服务端完成:

import { fetchOneEntry, getBuilderSearchParams } from '@builder.io/sdk-svelte'; import { BUILDER_PUBLIC_API_KEY } from '../../../apiKey'; import { getLocaleFromPathname } from '../../../utils'; /** @type {import('./$types').PageServerLoad} */ export async function load(event) { const locale = getLocaleFromPathname(event.url.pathname); // remove locale from the path to match multiple locales in same page if needed const urlPath = event.url.pathname.replace(`/${locale}`, '') || '/'; // fetch your Builder content const content = await fetchOneEntry({ model: 'page', apiKey: BUILDER_PUBLIC_API_KEY, locale, options: getBuilderSearchParams(event.url.searchParams), userAttributes: { urlPath, locale } }); return { content, locale, ...(!content && { status: 404 }) }; }

要点拆解:

  • fetchOneEntry是 SDK 提供的"抓取单条内容"API,参数model: 'page'对应在 Builder 中配置的模型名,apiKey传入公钥,locale传入当前语言;
  • locale剥离event.url.pathname.replace(/${locale}, '') || '/'把语言前缀从路径中移除,得到不带语言前缀的urlPath。注释说明了意图——如果多个语言共用同一页面,可以据此让不同语言的 URL 映射到同一条 Builder 内容
  • getBuilderSearchParams(event.url.searchParams)把当前 URL 的查询参数透传给 Builder,用于预览模式等场景;
  • userAttributes传入urlPathlocale,是 Builder 内容定位(targeting)与实验分流的依据;
  • 404 兜底...(!content && { status: 404 })—— 当抓不到内容时,load 返回的status: 404会让 SvelteKit 渲染出 404 响应。

七、页面渲染:Content组件、预览态与自定义组件注册

服务端返回的content会流入 src/routes/[lang]/[...catchall]/+page.svelte。该文件同时示范了三个 Builder SDK 的核心能力:渲染内容、预览模式、注册自定义组件。

1. 注册自定义组件

import Counter from '../../../lib/Counter.svelte'; // Create an array of your custom components and their properties const CUSTOM_COMPONENTS = [ { component: Counter, name: 'Counter', inputs: [ { name: 'name', type: 'text', defaultValue: 'hello' }, { name: 'count', type: 'number', defaultValue: 0 }, { name: 'localizeExample', type: 'text', localized: true } ] } ];

每个自定义组件声明包含component(组件实现)、name(在 Builder 编辑器中显示的名称)和inputs(属性 schema)。其中localizeExamplelocalized: true是本地化示例的关键:标记为localized的输入字段,Builder 会按语言分别存储其值,从而让同一组件在不同语言页面展示不同文案。defaultValue则提供了组件属性的默认值。

2. 渲染 Builder 内容

<script> import { isPreviewing, Content } from '@builder.io/sdk-svelte'; import { BUILDER_PUBLIC_API_KEY } from '../../../apiKey'; // this data comes from the function in `+page.server.js`, which runs on the server only export let data; // we want to show unpublished content when in preview mode. const canShowContent = data.content || isPreviewing(); const locale = data.locale; </script> <main> {#if canShowContent} <div>page Title: {data.content?.data?.title || 'Unpublished'}</div> <Content locale={locale} model="page" content={data.content} apiKey={BUILDER_PUBLIC_API_KEY} customComponents={CUSTOM_COMPONENTS} /> {:else} Content Not Found {/if} </main>
  • <Content>组件:接收modelcontentapiKeylocalecustomComponents五个关键 props,负责把服务端抓取到的内容树渲染为真实 DOM;
  • isPreviewing():SDK 提供的预览态判断。canShowContent = data.content || isPreviewing()的含义是——正式发布的内容直接渲染;若没有内容但正处于 Builder 预览模式,也允许展示(以便在编辑器中预览尚未发布的内容)。注意data注释明确说明它来自+page.server.jsload,只在服务端运行;
  • 空态处理:既无内容又非预览时,页面显示Content Not Found

八、本地化自定义组件:Counter 示例

src/lib/Counter.svelte 是一个带弹性动画的计数器组件,接收三个 props:namecountlocalizeExample

<script> import { spring } from 'svelte/motion'; export let name; export let count = 0; export let localizeExample; const displayed_count = spring(); $: displayed_count.set(count); $: offset = modulo($displayed_count, 1); function modulo(n, m) { // handle negative numbers return ((n % m) + m) % m; } </script> <div class="counter"> {name} - {localizeExample} ... </div>

值得注意的实现细节:

  • 使用svelte/motionspring让数字滚动过渡,并借助modulo函数处理负数取模,保证滚动方向正确;
  • 渲染时同时展示{name} - {localizeExample},直观验证"非本地化字段(name,各语言相同)"与"本地化字段(localizeExample,各语言不同)"的差异——这正是本示例演示本地化输入(localized: true)价值的地方;
  • 该组件在+page.svelte中通过CUSTOM_COMPONENTS注册后,便可以在 Builder 的可视化编辑器中像内置组件一样被拖拽、配置。

九、SDK 状态与特性支持

原文档指出,Svelte SDK 各特性的实现状态可查阅 packages/sdks/README.md 中的特性实现表格(Feature Implementation)。仓库中 SDK 的构建与输出目录位于 packages/sdks/output/svelte,本示例直接以 npm 包@builder.io/sdk-svelte形式依赖它。若你需要了解该 SDK 支持哪些功能(如 A/B 测试、个性化、数据绑定等)以及当前实现进度,以该表格为准。

十、完整接入清单(实操小结)

把以上内容收敛为接入 Builder 本地化 SvelteKit 站点的步骤清单:

  1. 在 Builder 空间配置好语言列表(与代码中supportedLocales一致),并创建名为page的模型;
  2. 复制公钥填入 src/apiKey.js;
  3. 建立src/routes/[lang]/[...catchall]/路由,在 src/utils.js 中声明supportedLocalesdefaultLocale
  4. 在 src/hooks.server.js 中实现语言判定与 301 重定向,并通过transformPageChunk注入<html lang>
  5. +page.server.js中用fetchOneEntrylocaleurlPath抓取内容,并在抓取失败时返回 404;
  6. +page.svelte中用<Content>渲染内容,用isPreviewing()支持预览未发布内容,并通过CUSTOM_COMPONENTS注册自定义组件;
  7. 将需要按语言区分的组件输入字段标记为localized: true
  8. 若 SDK 引入导致 Vite 预打包报错,参照 vite.config.js 在optimizeDeps.exclude中排除isolated-vm

至此,你便拥有了一个"可视化编辑 + 多语言分发 + 服务端渲染"完整可用的 Builder.io × SvelteKit 本地化站点骨架。

【免费下载链接】builderVisual Development for React, Vue, Svelte, Qwik, and more项目地址: https://gitcode.com/GitHub_Trending/bu/builder

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 21:27:53

Flutter iOS上线必做:Xcode原生层混淆配置全指南

1. 项目概述&#xff1a;为什么Flutter iOS打包必须做混淆&#xff1f;这不是“可选项”&#xff0c;而是上线前的硬门槛 你刚用Flutter写完一个功能完整的iOS应用&#xff0c;Xcode里点几下Archive、Export&#xff0c;生成.ipa文件&#xff0c;兴冲冲上传到TestFlight——结…

作者头像 李华
网站建设 2026/9/16 21:27:42

汽车LED专利战升级:从艾迈斯欧司朗维权看合规排雷策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 21:26:14

PaddleOCR GPU 部署全指南:从环境配置到 FastAPI 服务化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 21:23:57

CMSIS-NN模块裁剪与构建验证:嵌入式AI推理引擎源码尽调指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 21:23:20

SpringBoot宠物寄养系统设计与关键技术实现

1. 项目概述&#xff1a;SpringBoot宠物寄养系统的核心价值养宠人群的临时出行需求催生了专业的宠物托管市场。这个基于SpringBoot的宠物寄养系统&#xff0c;本质上解决的是"主人外出时宠物谁来管"的痛点问题。不同于简单的信息发布平台&#xff0c;我们设计的是一套…

作者头像 李华
网站建设 2026/9/16 21:22:42

Fizzy Cards API 实战指南:看板卡片的全生命周期管理

Fizzy Cards API 实战指南&#xff1a;看板卡片的全生命周期管理 【免费下载链接】fizzy Kanban as it should be. Not as it has been. 项目地址: https://gitcode.com/GitHub_Trending/fizzy2/fizzy 卡片&#xff08;Card&#xff09;是 Fizzy 看板中任务与工作项的基…

作者头像 李华