shopify-app-i18n

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Internationalization (i18n) for Shopify Apps

Shopify应用国际化(i18n)指南

Shopify merchants exist globally. Your app MUST support multiple languages to be featured or widely adopted.
Shopify商家遍布全球,你的应用必须支持多语言才能获得推荐或被广泛采用。

1. Stack

1. 技术栈

  • Library:
    i18next
    (Standard)
  • React:
    react-i18next
  • Remix:
    remix-i18next
  • 依赖库
    i18next
    (行业标准方案)
  • React适配
    react-i18next
  • Remix适配
    remix-i18next

2. Setup

2. 环境搭建

Installation

安装

bash
npm install i18next react-i18next remix-i18next i18next-fs-backend i18next-http-backend
bash
npm install i18next react-i18next remix-i18next i18next-fs-backend i18next-http-backend

Configuration (
app/i18n.server.ts
)

配置(
app/i18n.server.ts

Create a server-side instance to detect language.
typescript
import { RemixI18Next } from "remix-i18next/server";
import { createInstance } from "i18next";
import i18n from "~/i18n"; // client config
import { resolve } from "node:path";

export const i18nServer = new RemixI18Next({
  detection: {
    supportedLanguages: i18n.supportedLngs,
    fallbackLanguage: i18n.fallbackLng,
  },
  // This is the configuration for i18next
  i18next: {
    ...i18n,
    backend: {
      loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
    },
  },
});
创建服务端实例用于检测语种。
typescript
import { RemixI18Next } from "remix-i18next/server";
import { createInstance } from "i18next";
import i18n from "~/i18n"; // client config
import { resolve } from "node:path";

export const i18nServer = new RemixI18Next({
  detection: {
    supportedLanguages: i18n.supportedLngs,
    fallbackLanguage: i18n.fallbackLng,
  },
  // This is the configuration for i18next
  i18next: {
    ...i18n,
    backend: {
      loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
    },
  },
});

Root Loader (
app/root.tsx
)

根加载器(
app/root.tsx

Inject the locale into the document.
typescript
export async function loader({ request }: LoaderFunctionArgs) {
  const locale = await i18nServer.getLocale(request);
  return json({ locale });
}

export const handle = {
  // In the handle export, we can add a reference to a translation namespace
  // This will load the translations for this namespace for this route
  i18n: "common",
};

export default function App() {
  const { locale } = useLoaderData<typeof loader>();
  useChangeLanguage(locale); // Syncs remix locale with i18next
  
  return (
    <html lang={locale} dir={i18n.dir(locale)}>
      {/* ... */}
    </html>
  );
}
将语种信息注入到文档中。
typescript
export async function loader({ request }: LoaderFunctionArgs) {
  const locale = await i18nServer.getLocale(request);
  return json({ locale });
}

export const handle = {
  // In the handle export, we can add a reference to a translation namespace
  // This will load the translations for this namespace for this route
  i18n: "common",
};

export default function App() {
  const { locale } = useLoaderData<typeof loader>();
  useChangeLanguage(locale); // Syncs remix locale with i18next
  
  return (
    <html lang={locale} dir={i18n.dir(locale)}>
      {/* ... */}
    </html>
  );
}

3. Translation Files

3. 翻译文件

Store JSON files in
public/locales
.
public/
  locales/
    en/
      common.json
    fr/
      common.json
    vi/
      common.json
Example
common.json
:
json
{
  "welcome": "Welcome to my app",
  "dashboard": {
    "title": "Dashboard",
    "stats": "Statistics"
  }
}
将JSON文件存储在
public/locales
目录下。
public/
  locales/
    en/
      common.json
    fr/
      common.json
    vi/
      common.json
common.json
示例
:
json
{
  "welcome": "Welcome to my app",
  "dashboard": {
    "title": "Dashboard",
    "stats": "Statistics"
  }
}

4. Usage in Components

4. 在组件中使用

typescript
import { useTranslation } from "react-i18next";

export function DashboardHeader() {
  const { t } = useTranslation("common");

  return (
    <Page title={t("dashboard.title")}>
      <p>{t("welcome")}</p>
    </Page>
  );
}
typescript
import { useTranslation } from "react-i18next";

export function DashboardHeader() {
  const { t } = useTranslation("common");

  return (
    <Page title={t("dashboard.title")}>
      <p>{t("welcome")}</p>
    </Page>
  );
}

5. Detecting Shopify Admin Language

5. 检测Shopify Admin语种

Shopify passes the locale in the URL query params (
?locale=fr-FR
) or you can fetch it from the GraphQL Admin API (
Shop.billingAddress.country
or similar, though strictly speaking the Admin UI language is preferred).
Typically,
remix-i18next
detection handles the request headers/query params automatically if configured correctly.
Shopify会将语种信息放在URL查询参数中(
?locale=fr-FR
),你也可以从GraphQL Admin API获取该信息(可查询
Shop.billingAddress.country
或类似字段,不过严格来说优先使用Admin UI的语种设置)。
通常配置正确的情况下,
remix-i18next
的检测逻辑会自动处理请求头和查询参数中的语种信息。

6. Translating App Extensions

6. 应用扩展的翻译

For Theme App Extensions or Checkout UI Extensions, they have their own
locales
folder in their directory. They do NOT share the Remix app's locales.
  • Theme Extension:
    extensions/my-ext/locales/en.default.json
  • Checkout UI:
    extensions/checkout-ui/locales/en.json
对于主题应用扩展或者结账UI扩展,它们的目录下有独立的
locales
文件夹,不会共用Remix应用的本地化文件。
  • 主题扩展
    extensions/my-ext/locales/en.default.json
  • 结账UI扩展
    extensions/checkout-ui/locales/en.json