Bài 32.1: Cấu hình i18n trong Next.js

Chào mừng trở lại với chuỗi bài viết về lập trình Web Front-end! Hôm nay, chúng ta sẽ khám phá một chủ đề cực kỳ quan trọng khi xây dựng các ứng dụng web hiện đại: Internationalization (i18n), hay còn gọi là quốc tế hóa. Việc hỗ trợ đa ngôn ngữ giúp website hoặc ứng dụng của bạn tiếp cận được lượng lớn người dùng trên toàn cầu, mở rộng phạm vi và nâng cao trải nghiệm người dùng.

Và tin vui là, Next.js, framework mà chúng ta đang tìm hiểu, cung cấp sự hỗ trợ mạnh mẽtích hợp sẵn cho i18n, giúp việc triển khai trở nên đơn giản hơn bao giờ hết.

I18n là gì? Tại sao lại cần Internationalization và Localization?

Trước khi đi sâu vào cách cấu hình trong Next.js, hãy cùng làm rõ một chút về khái niệm:

  1. Internationalization (i18n - Quốc tế hóa): Là quá trình thiết kế và phát triển ứng dụng theo cách mà nó có thể thích ứng dễ dàng với các ngôn ngữ và vùng địa lý khác nhau mà không cần thay đổi code lõi. Chữ "18" ở giữa i và n là vì có 18 chữ cái giữa hai chữ này.
  2. Localization (L10n - Bản địa hóa): Là quá trình thực hiện sự thích ứng đó cho một ngôn ngữ và vùng địa lý cụ thể. Điều này bao gồm việc dịch văn bản, định dạng ngày giờ, tiền tệ, số, sắp xếp danh sách, v.v., sao cho phù hợp với văn hóa địa phương. Chữ "10" ở giữa L và n là vì có 10 chữ cái giữa hai chữ này.

Nói một cách đơn giản: i18n là khả năng hỗ trợ nhiều ngôn ngữ, còn L10n là việc làm cụ thể để hỗ trợ từng ngôn ngữ đó.

Tại sao cần chúng?

  • Tiếp cận thị trường toàn cầu: Mở rộng đối tượng người dùng.
  • Tăng trải nghiệm người dùng: Người dùng thích sử dụng sản phẩm bằng ngôn ngữ của họ.
  • Tăng khả năng cạnh tranh: Đứng vững hơn trên thị trường quốc tế.

Cấu hình i18n trong Next.js

Next.js xử lý i18n bằng cách định tuyến (routing) dựa trên locale. Điều này có nghĩa là mỗi ngôn ngữ bạn hỗ trợ sẽ có một URL riêng, thường được biểu thị bằng tiền tố (prefix) trong đường dẫn (ví dụ: /en/about, /vi/ve-chung-toi).

Việc cấu hình i18n được thực hiện trong file next.config.js ở thư mục gốc của dự án.

Bước 1: Cấu hình next.config.js

Bạn cần thêm một object i18n vào cấu hình của mình. Object này có ba thuộc tính chính:

  • locales: Một mảng các chuỗi, chứa mã ngôn ngữ/vùng địa lý được hỗ trợ (ví dụ: 'en', 'vi', 'en-US', 'fr'). Next.js sử dụng các mã BCP 47.
  • defaultLocale: Chuỗi, là mã ngôn ngữ mặc định. Khi người dùng truy cập trang mà không có tiền tố locale, Next.js sẽ sử dụng locale này.
  • localeDetection (tùy chọn): Boolean, mặc định là true. Nếu là true, Next.js sẽ tự động phát hiện ngôn ngữ ưa thích của người dùng từ header Accept-Language và chuyển hướng họ đến locale tương ứng (nếu có trong locales). Nếu là false, Next.js sẽ không tự động chuyển hướng mà chỉ dùng defaultLocale khi không có tiền tố.

Đây là ví dụ về cấu hình cơ bản:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  i18n: {
    // Danh sách các ngôn ngữ (locales) mà ứng dụng của bạn hỗ trợ
    locales: ['en', 'vi', 'fr'],

    // Ngôn ngữ mặc định nếu không có locale nào được chỉ định trong URL
    defaultLocale: 'vi',

    // Tùy chọn: Bật/tắt tự động phát hiện ngôn ngữ từ header Accept-Language
    // và chuyển hướng người dùng đến locale phù hợp
    localeDetection: true,
  },
  // ... các cấu hình Next.js khác của bạn có thể ở đây ...
}

module.exports = nextConfig

Giải thích code:

  • Chúng ta export một object cấu hình Next.js.
  • Thuộc tính i18n chứa cài đặt quốc tế hóa.
  • locales: ['en', 'vi', 'fr'] khai báo rằng ứng dụng này hỗ trợ tiếng Anh, tiếng Việt và tiếng Pháp. Next.js sẽ tạo các route tương ứng như /en/..., /vi/..., /fr/....
  • defaultLocale: 'vi' chỉ định tiếng Việt là ngôn ngữ mặc định. Nếu người dùng truy cập /about, họ sẽ thấy phiên bản tiếng Việt (URL vẫn là /about, không có tiền tố /vi/).
  • localeDetection: true cho phép Next.js kiểm tra header Accept-Language của trình duyệt. Nếu trình duyệt yêu cầu frfr có trong locales, người dùng truy cập trang gốc (/) có thể sẽ được chuyển hướng đến /fr.

Sau khi thêm cấu hình này và chạy lại ứng dụng (npm run dev hoặc yarn dev), Next.js sẽ tự động xử lý việc định tuyến dựa trên locale.

Bước 2: Routing và Lấy Locale Hiện Tại

Với cấu hình trên, Next.js sẽ tự động tạo các route cho từng locale. Ví dụ, nếu bạn có trang /pages/about.js, Next.js sẽ xử lý các URL như:

  • /about (sử dụng defaultLocale, ở đây là vi)
  • /en/about (sử dụng locale en)
  • /vi/about (sử dụng locale vi)
  • /fr/about (sử dụng locale fr)

Bạn có thể lấy thông tin về locale hiện tại trong các component React bằng hook useRouter từ next/router.

// pages/about.js (hoặc bất kỳ component nào)
import { useRouter } from 'next/router';
import Link from 'next/link'; // Import Link để minh họa chuyển locale

function AboutPage() {
  const router = useRouter();
  const { locale, locales, defaultLocale, asPath } = router;

  return (
    <div>
      <h1>Trang Về Chúng Tôi</h1>
      <p>Locale hiện tại: <strong>{locale}</strong></p>
      <p>Các locale được hỗ trợ: {locales?.join(', ')}</p>
      <p>Locale mặc định: {defaultLocale}</p>
      <p>Path hiện tại (bao gồm locale): {asPath}</p>

      {/* Ví dụ về Link sử dụng locale */}
      <p>
        <Link href="/">
          Quay về Trang chủ (Locale hiện tại)
        </Link>
      </p>
      <p>
        {/* Chuyển hướng đến Trang chủ tiếng Anh */}
        <Link href="/" locale="en">
          Go to Home (in English)
        </Link>
      </p>
       <p>
        {/* Chuyển hướng đến Trang chủ tiếng Pháp */}
        <Link href="/" locale="fr">
          Aller à la page d'accueil (en Français)
        </Link>
      </p>
    </div>
  );
}

export default AboutPage;

Giải thích code:

  • Chúng ta import useRouter từ next/router.
  • Gọi useRouter() để lấy đối tượng router.
  • Đối tượng router cung cấp các thuộc tính hữu ích như locale (locale hiện tại), locales (danh sách tất cả các locales được cấu hình), defaultLocale (locale mặc định), và asPath (đường dẫn hiển thị trên trình duyệt, bao gồm cả tiền tố locale nếu có).
  • Các thẻ <Link> từ next/link cũng hỗ trợ prop locale. Nếu không chỉ định locale, nó sẽ sử dụng locale hiện tại. Nếu chỉ định locale="en", nó sẽ tạo link đến phiên bản tiếng Anh của route đó (ví dụ: /en/).
Bước 3: Bản địa hóa Nội dung (Localization - L10n)

Cấu hình i18n trong next.config.js và việc định tuyến chỉ là khả năng hỗ trợ đa ngôn ngữ. Để thực sự hiển thị nội dung khác nhau cho từng ngôn ngữ, bạn cần triển khai phần L10n - tức là cung cấp các bản dịch.

Next.js không cung cấp một giải pháp dịch nội dung có sẵn (built-in). Bạn cần tự quản lý các bản dịch của mình. Cách phổ biến nhất là sử dụng các thư viện chuyên dụng hoặc tự tạo một hệ thống đơn giản.

Cách tiếp cận phổ biến:

  1. Sử dụng thư viện: Các thư viện như react-i18next hoặc next-i18next (được xây dựng dựa trên react-i18next và tích hợp tốt với Next.js) là lựa chọn được khuyến khích vì chúng cung cấp nhiều tính năng mạnh mẽ (quản lý file dịch, interpolation, fallback ngôn ngữ, hooks...).
  2. Tự triển khai đơn giản: Với các ứng dụng nhỏ, bạn có thể tự quản lý các object chứa chuỗi dịch và sử dụng hook useRouter để lấy locale hiện tại, sau đó chọn đúng object dịch.

Hãy minh họa cách tự triển khai rất đơn giản (chỉ để hiểu concept, không dùng cho ứng dụng lớn):

Đầu tiên, tạo các file chứa bản dịch cho từng ngôn ngữ. Ví dụ, trong thư mục src/locales:

// src/locales/en.js
const en = {
  home: {
    title: 'Welcome to our site!',
    greeting: 'Hello, user!',
    description: 'This is a multi-language example in Next.js.',
    learnMore: 'Learn More',
  },
  about: {
    title: 'About Us',
    content: 'We are a company dedicated to...',
  },
  // ... các phần khác
};
export default en;
// src/locales/vi.js
const vi = {
  home: {
    title: 'Chào mừng đến với trang web của chúng tôi!',
    greeting: 'Xin chào, người dùng!',
    description: 'Đây là ví dụ đa ngôn ngữ trong Next.js.',
    learnMore: 'Tìm hiểu thêm',
  },
  about: {
    title: 'Về Chúng Tôi',
    content: 'Chúng tôi là một công ty chuyên về...',
  },
   // ... các phần khác
};
export default vi;

Tiếp theo, trong component của bạn, sử dụng useRouter để lấy locale và truy cập bản dịch phù hợp:

// pages/index.js (hoặc component Trang Chủ)
import { useRouter } from 'next/router';
import Link from 'next/link';

// Import các file dịch (hoặc có cơ chế tải động tốt hơn)
// Trong thực tế, bạn sẽ dùng Context hoặc thư viện để quản lý state này
import en from '../locales/en';
import vi from '../locales/vi';

// Tập hợp tất cả các bản dịch
const translations = {
  en,
  vi,
};

function HomePage() {
  const router = useRouter();
  const { locale } = router;

  // Lấy object bản dịch cho locale hiện tại, fallback về defaultLocale nếu không tìm thấy
  const t = translations[locale] || translations[router.defaultLocale];

  // Xử lý trường hợp bản dịch chưa sẵn sàng hoặc locale không hợp lệ (tùy ứng dụng)
  if (!t) {
      return <div>Loading translations...</div>; // Hoặc hiển thị nội dung mặc định
  }

  return (
    <div>
      <h1>{t.home.title}</h1>
      <p>{t.home.description}</p>
      <p>{t.home.greeting}</p>
      <p>
        <Link href="/about">{t.home.learnMore}</Link>
      </p>

      {/* Component chuyển đổi ngôn ngữ (xem phần tiếp theo) */}
       <LanguageSwitcher />
    </div>
  );
}

export default HomePage;

Giải thích code:

  • Chúng ta import các object bản dịch cho tiếng Anh (en) và tiếng Việt (vi).
  • Tạo một object translations để dễ dàng truy cập bản dịch theo locale.
  • Trong component, dùng useRouter để lấy locale hiện tại.
  • Sử dụng locale để chọn đúng object bản dịch từ translations. Ta dùng fallback || translations[router.defaultLocale] để đảm bảo luôn có bản dịch (ít nhất là bản dịch mặc định).
  • Truy cập các chuỗi dịch bằng cách dùng key (ví dụ: t.home.title).

Cách làm này chỉ là minh họa. Với các ứng dụng thực tế có nhiều nội dung và nhiều ngôn ngữ, việc quản lý các file dịch, tải chúng, và xử lý các trường hợp phức tạp (số nhiều, ngày tháng, tham số trong chuỗi...) đòi hỏi một thư viện i18n chuyên nghiệp.

Bước 4: Tạo Component Chuyển Đổi Ngôn Ngữ

Để người dùng có thể tự chọn ngôn ngữ, bạn cần tạo một component đơn giản cho phép họ làm điều đó. Cách dễ nhất là sử dụng component <Link> với prop locale.

// components/LanguageSwitcher.js
import Link from 'next/link';
import { useRouter } from 'next/router';

function LanguageSwitcher() {
  const router = useRouter();
  const { locales, locale: currentLocale, asPath } = router; // Lấy danh sách locales, locale hiện tại và path

  return (
    <div>
      <p>Chọn ngôn ngữ:</p>
      <ul>
        {/* Duyệt qua danh sách các locales được cấu hình */}
        {locales?.map((locale) => (
          <li key={locale}>
            {/* Tạo Link đến cùng một path hiện tại, nhưng với locale khác */}
            <Link href={asPath} locale={locale}>
              {/* Hiển thị tên ngôn ngữ hoặc mã locale (có thể làm đẹp hơn) */}
              {locale.toUpperCase()}
            </Link>
             {/* Có thể thêm dấu hiệu nhận biết ngôn ngữ hiện tại */}
             {locale === currentLocale && <span style={{ fontWeight: 'bold', marginLeft: '5px' }}>(Hiện tại)</span>}
          </li>
        ))}
      </ul>
       {/* Minh họa chuyển về locale mặc định (nếu không phải locale hiện tại) */}
      {currentLocale !== router.defaultLocale && (
           <p>
                <Link href={asPath} locale={router.defaultLocale}>
                   Quay về {router.defaultLocale.toUpperCase()} (Mặc định)
                </Link>
           </p>
      )}
    </div>
  );
}

export default LanguageSwitcher;

Giải thích code:

  • Import LinkuseRouter.
  • Lấy danh sách locales, locale hiện tại (currentLocale), và asPath (đường dẫn đầy đủ hiện tại) từ router.
  • Duyệt qua mảng locales.
  • Với mỗi locale, tạo một thẻ <Link>.
    • href={asPath}: Giữ nguyên đường dẫn hiện tại (ví dụ: /about hoặc /en/about).
    • locale={locale}: Chỉ định locale mà link này sẽ chuyển đến. Next.js sẽ tự động thay đổi tiền tố URL (ví dụ: từ /en/about sang /vi/about nếu bấm vào link tiếng Việt).
  • Hiển thị mã locale (có thể thay bằng tên ngôn ngữ đầy đủ nếu cần).
  • Thêm một dấu hiệu nhỏ để người dùng biết ngôn ngữ nào đang được chọn.

Bạn có thể đặt component LanguageSwitcher này ở bất kỳ đâu trong ứng dụng của mình (ví dụ: header, footer).

Các Điểm Cần Lưu Ý

  • Server-Side Rendering (SSR) và Static Site Generation (SSG): Next.js i18n hoạt động tốt với cả SSR và SSG. Với SSG, Next.js sẽ tạo ra các phiên bản tĩnh của mỗi trang cho mỗi locale được cấu hình. Điều này có thể làm tăng đáng kể số lượng trang tĩnh được tạo ra.
  • Dynamic Routes: i18n cũng hoạt động với dynamic routes. Ví dụ: /pages/products/[slug].js sẽ có các biến thể như /en/products/[slug], /vi/products/[slug]. Khi lấy params trong getStaticPaths hoặc getServerSideProps, bạn cũng sẽ nhận được thông tin về locale hiện tại.
  • Sử dụng thư viện: Như đã đề cập, với các ứng dụng lớn, nên sử dụng các thư viện i18n chuyên dụng để quản lý bản dịch một cách hiệu quả. next-i18next là một lựa chọn phổ biến vì nó kết hợp react-i18next và tích hợp tốt với các tính năng của Next.js (đặc biệt là SSR/SSG).
  • Locale Detection: Việc tự động phát hiện locale (localeDetection: true) dựa vào header Accept-Language có thể không chính xác 100% và có thể gây ra chuyển hướng không mong muốn cho một số người dùng. Hãy cân nhắc cẩn thận khi bật tính năng này.

Comments

There are no comments at the moment.