MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SvelteKit 路由与国际化:实现多语言支持的路由方案

2024-10-245.5k 阅读

SvelteKit 路由基础

在探讨 SvelteKit 中实现多语言支持的路由方案之前,我们先来回顾一下 SvelteKit 的路由基础。SvelteKit 采用文件系统路由,这意味着项目目录结构直接映射到应用的路由。

例如,在项目根目录下的 src/routes 文件夹中,每个文件和文件夹都对应一个路由。如果有一个 src/routes/about.svelte 文件,那么就会生成一个 /about 的路由。如果是一个文件夹 src/routes/products,并且该文件夹下有 +page.svelte 文件,那么就会生成 /products 路由。

这种基于文件系统的路由方式非常直观,易于理解和维护。它使得路由的创建和管理变得简单,开发者可以通过直接创建文件和文件夹来定义新的路由。

动态路由

SvelteKit 还支持动态路由。动态路由在处理具有相似结构但不同参数的页面时非常有用。例如,在一个博客应用中,每个文章都有一个唯一的 ID,我们可以通过动态路由来处理不同文章的展示。

src/routes/blog 文件夹下创建一个 [id].svelte 文件,这里的 [id] 就是动态参数。当访问 /blog/123 时,id 的值就是 123。在 [id].svelte 文件中,可以通过 $page.params.id 来获取这个动态参数的值。

<script>
    import { page } from '$app/stores';
    const { id } = $page.params;
</script>

<h1>Article {id}</h1>

嵌套路由

嵌套路由在 SvelteKit 中也很容易实现。假设我们有一个电商应用,产品详情页面可能有不同的标签页,如描述、规格、评论等。我们可以通过嵌套路由来实现这种结构。

src/routes/products 文件夹下,除了 +page.svelte 文件外,我们再创建一个 [productId] 文件夹。在这个文件夹下,可以创建 +page.svelte 用于展示产品详情的基本信息,还可以创建 description/+page.sveltespecs/+page.sveltereviews/+page.svelte 等文件来处理不同标签页的内容。

当访问 /products/123/description 时,就会加载 src/routes/products/[productId]/description/+page.svelte 文件的内容。

国际化基础概念

国际化(i18n)是指设计和开发能够适应不同语言和地区的应用程序的过程。在前端开发中,实现国际化主要涉及到文本翻译、日期和数字格式调整、语言特定的布局等方面。

在 SvelteKit 应用中实现国际化,我们首先需要解决文本翻译的问题。这意味着我们要根据用户选择的语言,加载相应语言的文本内容并在页面上正确显示。

选择国际化库

在 Svelte 生态系统中,有几个流行的国际化库可供选择,如 svelte-i18ni18next 等。svelte-i18n 是专门为 Svelte 设计的国际化库,它与 Svelte 的语法和特性紧密结合,使用起来较为方便。i18next 则是一个功能强大、跨框架的国际化库,具有丰富的插件生态系统。

在本文中,我们将以 svelte-i18n 为例来实现 SvelteKit 应用的国际化。

安装和配置 svelte-i18n

首先,通过 npm 安装 svelte-i18n

npm install svelte-i18n

然后,在项目的入口文件(通常是 src/main.js)中进行配置。

import { writable } from'svelte/store';
import { init, setLocale, locales, getLocaleFromNavigator } from'svelte-i18n';

// 定义支持的语言
const supportedLocales = ['en', 'zh'];

// 初始化国际化
init({
    fallbackLocale:'en',
    initialLocale: getLocaleFromNavigator(supportedLocales) || 'en'
});

// 创建一个可写的存储来保存当前语言
export const currentLocale = writable(locales[0]);

// 监听当前语言的变化并更新
currentLocale.subscribe((locale) => {
    setLocale(locale);
});

加载翻译文件

我们需要为每种支持的语言创建翻译文件。通常,这些文件可以放在 src/locales 文件夹下。例如,对于英语(en),创建 src/locales/en.json 文件:

{
    "welcome": "Welcome to our app",
    "about": "About us"
}

对于中文(zh),创建 src/locales/zh.json 文件:

{
    "welcome": "欢迎来到我们的应用",
    "about": "关于我们"
}

在 Svelte 组件中,我们可以通过 svelte-i18n_ 函数来获取翻译后的文本。首先,在组件中导入 _ 函数:

<script>
    import { _ } from'svelte-i18n';
</script>

<h1>{_('welcome')}</h1>

实现多语言支持的路由方案

基于 URL 参数的多语言路由

一种常见的实现多语言支持路由的方式是通过 URL 参数。例如,我们可以在 URL 中添加 lang 参数来指定语言。

在 SvelteKit 中,我们可以在路由加载时获取这个参数并设置相应的语言。首先,修改路由文件(如 src/routes/+layout.svelte):

<script>
    import { page } from '$app/stores';
    import { currentLocale } from '$lib/main';
    import { setLocale } from'svelte-i18n';

    $: {
        const lang = $page.url.searchParams.get('lang');
        if (lang) {
            currentLocale.set(lang);
            setLocale(lang);
        }
    }
</script>

{#if $page.route.id === '/'}
    <a href="?lang=en">English</a>
    <a href="?lang=zh">中文</a>
{/if}

{#if $page.route.id === '/about'}
    <a href="/about?lang=en">English</a>
    <a href="/about?lang=zh">中文</a>
{/if}

{#await load()}
    <p>Loading...</p>
{:then data}
    {#if data.error}
        <p>{data.error.message}</p>
    {:else}
        {#if data.pages}
            {#each data.pages as page}
                {#await page.render()}
                    <p>Loading page...</p>
                {:then html}
                    {@html html}
                {/await}
            {/each}
        {/if}
    {/if}
{/await}

在这个例子中,我们通过 $page.url.searchParams.get('lang') 获取 URL 中的 lang 参数。如果参数存在,就设置当前语言。同时,在每个页面上提供切换语言的链接,链接中包含相应的 lang 参数。

基于子路径的多语言路由

另一种方式是基于子路径来实现多语言路由。例如,/en/about 表示英文的关于页面,/zh/about 表示中文的关于页面。

首先,我们需要在 src/routes 文件夹下创建语言相关的文件夹,如 enzh。然后,将相应语言的页面文件放在这些文件夹中。例如,src/routes/en/about.sveltesrc/routes/zh/about.svelte

src/routes/+layout.svelte 中,我们可以获取当前路径的语言部分并设置相应的语言:

<script>
    import { page } from '$app/stores';
    import { currentLocale } from '$lib/main';
    import { setLocale } from'svelte-i18n';

    $: {
        const segments = $page.url.pathname.split('/');
        const lang = segments[1];
        if (['en', 'zh'].includes(lang)) {
            currentLocale.set(lang);
            setLocale(lang);
        }
    }
</script>

{#if $page.route.id === '/'}
    <a href="/en">English</a>
    <a href="/zh">中文</a>
{/if}

{#if $page.route.id === '/about'}
    <a href="/en/about">English</a>
    <a href="/zh/about">中文</a>
{/if}

{#await load()}
    <p>Loading...</p>
{:then data}
    {#if data.error}
        <p>{data.error.message}</p>
    {:else}
        {#if data.pages}
            {#each data.pages as page}
                {#await page.render()}
                    <p>Loading page...</p>
                {:then html}
                    {@html html}
                {/await}
            {/each}
        {/if}
    {/if}
{/await}

在这个例子中,我们通过 $page.url.pathname.split('/') 获取路径的 segments,然后判断第二个 segment 是否是支持的语言代码。如果是,就设置当前语言。同样,在页面上提供切换语言的链接,链接使用基于子路径的方式。

处理语言切换时的页面更新

当用户切换语言时,我们希望页面能够及时更新为新语言的内容。在 SvelteKit 中,我们可以利用 Svelte 的响应式机制来实现这一点。

例如,在一个包含翻译文本的组件中:

<script>
    import { _ } from'svelte-i18n';
    import { currentLocale } from '$lib/main';

    let text;

    $: text = _('welcome');

    currentLocale.subscribe(() => {
        text = _('welcome');
    });
</script>

<h1>{text}</h1>

在这个例子中,我们通过订阅 currentLocale 的变化,当语言切换时,重新获取翻译后的文本并更新页面显示。

处理日期和数字格式

除了文本翻译,国际化还涉及到日期和数字格式的调整。在 SvelteKit 应用中,我们可以使用 Intl.DateTimeFormatIntl.NumberFormat 来处理这些问题。

例如,对于日期格式:

<script>
    import { currentLocale } from '$lib/main';
    const date = new Date();

    let formattedDate;

    $: {
        const locale = $currentLocale;
        formattedDate = new Intl.DateTimeFormat(locale, {
            year: 'numeric',
            month: 'long',
            day: 'numeric'
        }).format(date);
    }
</script>

<p>{formattedDate}</p>

对于数字格式:

<script>
    import { currentLocale } from '$lib/main';
    const number = 1234.56;

    let formattedNumber;

    $: {
        const locale = $currentLocale;
        formattedNumber = new Intl.NumberFormat(locale, {
            style: 'decimal',
            maximumFractionDigits: 2
        }).format(number);
    }
</script>

<p>{formattedNumber}</p>

通过这种方式,我们可以根据用户选择的语言,正确地显示日期和数字格式。

优化多语言路由的 SEO

在实现多语言支持的路由方案时,SEO 也是一个重要的考虑因素。搜索引擎需要能够理解不同语言页面之间的关系。

我们可以使用 hreflang 标签来告知搜索引擎不同语言版本页面的存在。在 SvelteKit 应用中,我们可以在 src/routes/+layout.svelte 文件中添加 hreflang 标签:

<script>
    import { page } from '$app/stores';
    import { currentLocale } from '$lib/main';
    import { setLocale } from'svelte-i18n';

    $: {
        const segments = $page.url.pathname.split('/');
        const lang = segments[1];
        if (['en', 'zh'].includes(lang)) {
            currentLocale.set(lang);
            setLocale(lang);
        }
    }
</script>

<head>
    {#if $page.route.id === '/'}
        <link rel="alternate" href="/en" hreflang="en">
        <link rel="alternate" href="/zh" hreflang="zh">
    {/if}

    {#if $page.route.id === '/about'}
        <link rel="alternate" href="/en/about" hreflang="en">
        <link rel="alternate" href="/zh/about" hreflang="zh">
    {/if}
</head>

{#if $page.route.id === '/'}
    <a href="/en">English</a>
    <a href="/zh">中文</a>
{/if}

{#if $page.route.id === '/about'}
    <a href="/en/about">English</a>
    <a href="/zh/about">中文</a>
{/if}

{#await load()}
    <p>Loading...</p>
{:then data}
    {#if data.error}
        <p>{data.error.message}</p>
    {:else}
        {#if data.pages}
            {#each data.pages as page}
                {#await page.render()}
                    <p>Loading page...</p>
                {:then html}
                    {@html html}
                {/await}
            {/each}
        {/if}
    {/if}
{/await}

通过添加 hreflang 标签,搜索引擎可以更好地理解不同语言页面之间的关系,从而提高多语言应用的 SEO 效果。

处理语言特定的布局

在某些情况下,不同语言可能需要不同的布局。例如,从右到左书写的语言(如阿拉伯语)可能需要与从左到右书写的语言(如英语)不同的布局。

在 SvelteKit 应用中,我们可以根据当前语言动态加载不同的布局文件。假设我们有一个 src/layouts 文件夹,其中包含 en.layout.sveltear.layout.svelte 文件。

src/routes/+layout.svelte 中:

<script>
    import { page } from '$app/stores';
    import { currentLocale } from '$lib/main';
    import { setLocale } from'svelte-i18n';

    let Layout;

    $: {
        const lang = $currentLocale;
        if (lang === 'en') {
            Layout = () => import('$lib/layouts/en.layout.svelte');
        } else if (lang === 'ar') {
            Layout = () => import('$lib/layouts/ar.layout.svelte');
        }
    }
</script>

{#if Layout}
    {#await Layout()}
        <p>Loading layout...</p>
    {:then LayoutComponent}
        <LayoutComponent>
            {#await load()}
                <p>Loading...</p>
            {:then data}
                {#if data.error}
                    <p>{data.error.message}</p>
                {:else}
                    {#if data.pages}
                        {#each data.pages as page}
                            {#await page.render()}
                                <p>Loading page...</p>
                            {:then html}
                                {@html html}
                            {/await}
                        {/each}
                    {/if}
                {/if}
            {/await}
        </LayoutComponent>
    {/await}
{/if}

通过这种方式,我们可以根据当前语言动态加载相应的布局文件,从而实现语言特定的布局。

测试多语言路由方案

在开发过程中,对多语言路由方案进行充分的测试是非常重要的。我们可以使用 Jest 和 Cypress 等测试框架来进行单元测试和端到端测试。

对于单元测试,我们可以测试语言切换功能是否正确更新翻译文本。例如,使用 Jest 和 svelte - test - utils

import { render, fireEvent } from '@testing-library/svelte';
import LanguageSwitcher from './LanguageSwitcher.svelte';
import { currentLocale } from '$lib/main';

describe('LanguageSwitcher', () => {
    it('should switch language', async () => {
        const { getByText } = render(LanguageSwitcher);
        const englishLink = getByText('English');
        await fireEvent.click(englishLink);
        expect(currentLocale.get()).toBe('en');
    });
});

对于端到端测试,我们可以使用 Cypress 来测试整个应用在不同语言下的页面加载和功能是否正常。例如:

describe('Multilingual Routing', () => {
    it('should load English page correctly', () => {
        cy.visit('/en');
        cy.contains('Welcome to our app');
    });

    it('should load Chinese page correctly', () => {
        cy.visit('/zh');
        cy.contains('欢迎来到我们的应用');
    });
});

通过这些测试,我们可以确保多语言路由方案在不同场景下都能正常工作。

部署多语言 SvelteKit 应用

在完成开发和测试后,我们需要将多语言 SvelteKit 应用部署到服务器上。部署过程与普通的 SvelteKit 应用类似,但需要注意一些与国际化相关的配置。

如果我们使用基于子路径的多语言路由,服务器需要正确配置以处理不同语言的子路径。例如,在使用 Node.js 和 Express 作为服务器时:

const express = require('express');
const app = express();
const path = require('path');

app.use('/en', express.static(path.join(__dirname, 'build/en')));
app.use('/zh', express.static(path.join(__dirname, 'build/zh')));

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

通过这种配置,服务器可以正确地为不同语言的页面提供服务。同时,我们还需要确保服务器正确设置了 Content - Language 等 HTTP 头信息,以告知客户端当前页面的语言。

在部署到云平台(如 Vercel、Netlify 等)时,也需要根据平台的文档进行相应的配置,以确保多语言路由方案能够正常工作。

总结与展望

通过上述步骤,我们在 SvelteKit 应用中实现了多语言支持的路由方案。从路由基础、国际化库的选择与配置,到基于 URL 参数和子路径的多语言路由实现,再到语言切换、日期数字格式处理、SEO 优化、语言特定布局、测试和部署等方面,全面地构建了一个支持多语言的前端应用。

未来,随着 Svelte 和 SvelteKit 的不断发展,国际化功能可能会更加完善和便捷。例如,可能会有更集成化的方式来处理国际化,减少手动配置的工作量。同时,随着用户对全球化应用需求的不断增加,多语言支持将成为前端应用开发的一个重要组成部分,我们需要不断关注和探索新的技术和方法来提升用户体验。