Next.js 15 Fullstack (App Router + Server Actions) · บทเรียน

การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา

ใช้ API ของ Intl เพื่อแสดงวันที่ สกุลเงิน และข้อความพหูพจน์ให้ถูกต้องตามภูมิภาคภาษา

บทเรียน 4 จาก 413 ขั้นตอน

การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา เป็นบทเรียน Next.js 15 Fullstack (App Router + Server Actions) ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Next.js 15 Fullstack (App Router + Server Actions) และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Next.js 15 Fullstack (App Router + Server Actions) มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

Why Locale-Aware Formatting Matters

A date like 06/07/2025 means June 7th to an American, but July 6th to a European. A number like 1.234 is one thousand two hundred thirty-four in the US, but one point two three four in Germany.

Hard-coding format logic breaks your app for international users. Instead, the browser and Node.js both ship the Intl namespace — a standards-based set of APIs that handle locale-aware formatting with zero third-party libraries.

  • Intl.DateTimeFormat — formats dates and times
  • Intl.NumberFormat — formats numbers, currencies, and units
  • Intl.PluralRules — selects the correct plural form

In a Next.js 15 App Router project these run on the server (in Server Components and Server Actions) as well as on the client, so you get consistent output without shipping extra JavaScript.

Detecting the Active Locale in App Router

Next.js 15 with the App Router stores the active locale in the URL segment. A common convention is /en/dashboard or /de/dashboard. Your layout.tsx receives a params prop that contains it.

The snippet below shows how to read params.locale in an async Server Component and pass it down to formatting helpers.

// app/[locale]/layout.tsx
import { ReactNode } from 'react';

interface Props {
  children: ReactNode;
  params: Promise<{ locale: string }>;
}

export default async function LocaleLayout({ children, params }: Props) {
  const { locale } = await params; // e.g. 'en', 'de', 'tr'

  return (
    <html lang={locale}>
      <body>{children}</body>
    </html>
  );
}

Formatting Dates with Intl.DateTimeFormat

Intl.DateTimeFormat accepts a locale string and an options object. You can control which date/time parts to show and in what style.

  • dateStyle: 'short', 'medium', 'long', 'full'
  • timeStyle: same options
  • Individual fields: year, month, day, hour, minute, etc.

Calling .format(date) returns a locale-correct string. Because Intl is built into Node.js, this runs safely inside a Server Component with no client bundle cost.

// lib/format-date.ts
export function formatDate(
  date: Date,
  locale: string,
  options: Intl.DateTimeFormatOptions = { dateStyle: 'long' }
): string {
  return new Intl.DateTimeFormat(locale, options).format(date);
}

// Usage examples:
// formatDate(new Date(), 'en-US') => 'June 7, 2025'
// formatDate(new Date(), 'de-DE') => '7. Juni 2025'
// formatDate(new Date(), 'tr-TR') => '7 Haziran 2025'
// formatDate(new Date(), 'ja-JP') => '2025年6月7日'

Using formatDate in a Server Component

Because Server Components run on Node.js, you can call your formatDate helper directly — no useEffect, no hydration mismatch, no extra bundle weight for the client.

The locale comes from params and is passed straight into the formatter. The result is a plain string embedded in HTML.

// app/[locale]/blog/[slug]/page.tsx
import { formatDate } from '@/lib/format-date';

interface Props {
  params: Promise<{ locale: string; slug: string }>;
}

export default async function BlogPost({ params }: Props) {
  const { locale, slug } = await params;

  // In a real app you would fetch the post from a DB or CMS
  const publishedAt = new Date('2025-06-07T09:00:00Z');

  return (
    <article>
      <time dateTime={publishedAt.toISOString()}>
        {formatDate(publishedAt, locale, { dateStyle: 'full', timeStyle: 'short' })}
      </time>
      <h1>Post: {slug}</h1>
    </article>
  );
}

Formatting Numbers and Currencies

Intl.NumberFormat handles three main use-cases:

  • Plain numbers — decimal separators and grouping differ by locale (1,234.56 vs 1.234,56)
  • Currency — symbol placement, decimal precision, and even currency name differ
  • Units — kilograms, miles, bytes, etc., formatted according to locale conventions

Always pass currency together with style: 'currency'. The currency code (ISO 4217) tells the formatter which symbol to use; the locale tells it where to put it.

// lib/format-number.ts
export function formatCurrency(
  amount: number,
  currency: string,
  locale: string
): string {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(amount);
}

export function formatNumber(
  value: number,
  locale: string,
  options?: Intl.NumberFormatOptions
): string {
  return new Intl.NumberFormat(locale, options).format(value);
}

// formatCurrency(1234.5, 'USD', 'en-US') => '$1,234.50'
// formatCurrency(1234.5, 'EUR', 'de-DE') => '1.234,50 €'
// formatCurrency(1234.5, 'TRY', 'tr-TR') => '₺1.234,50'

A Standalone Intl Demo (Runnable)

The snippet below is a complete, self-contained TypeScript program that demonstrates Intl.DateTimeFormat and Intl.NumberFormat across several locales. No framework or external package is needed — you can run it with ts-node or paste it into a TypeScript playground.

const locales = ['en-US', 'de-DE', 'tr-TR', 'ja-JP', 'ar-SA'];
const date = new Date('2025-06-07T14:30:00Z');
const price = 9999.99;

for (const locale of locales) {
  const formattedDate = new Intl.DateTimeFormat(locale, {
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(date);

  const formattedPrice = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'USD',
  }).format(price);

  console.log(`[${locale}]  date: ${formattedDate}  |  price: ${formattedPrice}`);
}

Plural Rules — the Hidden Complexity

English has two plural forms: 1 item vs 2 items. But Arabic has six, Russian has three, and Polish has four. If you build pluralization with a simple count === 1 ? 'item' : 'items' check, your app will be wrong for most of the world.

Intl.PluralRules solves this by returning the correct CLDR plural category for a given number and locale:

  • zero
  • one
  • two
  • few
  • many
  • other

You map each category to the correct translated string in your message catalog, then let PluralRules pick the right key at runtime.

Implementing a pluralize Helper

The helper below accepts a count, a locale, and a map of plural category strings. It uses Intl.PluralRules to select the right form, then replaces a {{count}} placeholder with the actual number.

This keeps plural logic out of your translation files — the files only need the per-category templates, not branching code.

// lib/pluralize.ts
type PluralForms = Partial<Record<Intl.LDMLPluralRule, string>>;

export function pluralize(
  count: number,
  locale: string,
  forms: PluralForms
): string {
  const rules = new Intl.PluralRules(locale);
  const category = rules.select(count); // 'zero'|'one'|'two'|'few'|'many'|'other'
  const template = forms[category] ?? forms['other'] ?? '';
  return template.replace('{{count}}', String(count));
}

// English
// pluralize(1, 'en', { one: '{{count}} item', other: '{{count}} items' })
// => '1 item'

// pluralize(5, 'en', { one: '{{count}} item', other: '{{count}} items' })
// => '5 items'

// Arabic (6 forms)
// pluralize(11, 'ar', { zero:'...', one:'...', two:'...', few:'...', many:'{{count}} عنصرًا', other:'...' })
// => '11 عنصرًا'

Using pluralize in a Server Component

Combine the pluralize helper with locale-aware number formatting for a complete, production-ready solution. The cart summary below shows item count and total price, both correctly formatted for any locale.

// app/[locale]/cart/page.tsx
import { pluralize } from '@/lib/pluralize';
import { formatCurrency } from '@/lib/format-number';

interface Props {
  params: Promise<{ locale: string }>;
}

// Minimal translation catalog — in real projects use next-intl or similar
const itemForms: Record<string, Record<string, string>> = {
  'en-US': { one: '{{count}} item',     other: '{{count}} items' },
  'de-DE': { one: '{{count}} Artikel',  other: '{{count}} Artikel' },
  'tr-TR': { one: '{{count}} ürün',     other: '{{count}} ürün' },
};

export default async function CartPage({ params }: Props) {
  const { locale } = await params;
  const itemCount = 3;
  const total = 129.99;
  const forms = itemForms[locale] ?? itemForms['en-US'];

  return (
    <section>
      <p>{pluralize(itemCount, locale, forms)}</p>
      <p>Total: {formatCurrency(total, 'USD', locale)}</p>
    </section>
  );
}

Relative Time Formatting with Intl.RelativeTimeFormat

Intl.RelativeTimeFormat formats durations relative to now — 3 days ago, in 2 hours — in any locale and any numeric style.

  • numeric: 'always' — always uses numbers: 1 day ago
  • numeric: 'auto' — uses natural language when possible: yesterday

The helper below computes the best unit automatically and delegates the string to the API.

// lib/format-relative.ts
export function formatRelative(date: Date, locale: string): string {
  const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
  const diffMs = date.getTime() - Date.now();
  const diffSec = Math.round(diffMs / 1000);

  const thresholds: [number, Intl.RelativeTimeFormatUnit][] = [
    [60, 'second'],
    [3600, 'minute'],
    [86400, 'hour'],
    [2592000, 'day'],
    [31536000, 'month'],
  ];

  for (const [limit, unit] of thresholds) {
    if (Math.abs(diffSec) < limit) {
      const divisor = unit === 'second' ? 1
        : unit === 'minute' ? 60
        : unit === 'hour' ? 3600
        : unit === 'day' ? 86400
        : 2592000;
      return rtf.format(Math.round(diffSec / divisor), unit);
    }
  }

  return rtf.format(Math.round(diffSec / 31536000), 'year');
}

// formatRelative(new Date(Date.now() - 90000), 'en-US') => '2 minutes ago'
// formatRelative(new Date(Date.now() - 90000), 'de-DE') => 'vor 2 Minuten'

Creating a Reusable FormattedDate Client Component

Sometimes you need date formatting on the client — for example, when the date changes after user interaction. A thin Client Component that reads navigator.language avoids hydration mismatches by letting the browser pick its own locale.

Notice the 'use client' directive. The component itself ships no Intl polyfill because all modern browsers and Node 18+ support the full Intl spec natively.

'use client';

import { useMemo } from 'react';

interface Props {
  date: string; // ISO string — safe to serialize from Server to Client
  options?: Intl.DateTimeFormatOptions;
}

export function FormattedDate({ date, options = { dateStyle: 'medium' } }: Props) {
  // navigator.language gives the browser's locale, e.g. 'en-GB'
  const formatted = useMemo(() => {
    return new Intl.DateTimeFormat(navigator.language, options).format(
      new Date(date)
    );
  }, [date, options]);

  return <time dateTime={date}>{formatted}</time>;
}

Knowledge Check: Plural Forms and Intl.PluralRules

Based on what you learned in this lesson, answer the following question about Intl.PluralRules and locale-aware pluralization.

Recap: Locale-Aware Formatting in Next.js 15

In this lesson you learned how to use the built-in Intl APIs to format dates, numbers, currencies, relative times, and pluralized strings correctly for any locale — with no third-party libraries required.

  • Intl.DateTimeFormat — formats dates and times; use dateStyle/timeStyle for quick output or individual field options for fine control.
  • Intl.NumberFormat — handles plain numbers, currencies (style: 'currency'), and units; always pair a currency code with the locale.
  • Intl.RelativeTimeFormat — produces locale-correct relative strings like yesterday or vor 2 Minuten.
  • Intl.PluralRules — selects the CLDR plural category (zero / one / two / few / many / other) so your translation catalog can provide the right form for every language.
  • In Next.js 15 App Router, run these formatters in Server Components to avoid hydration mismatches and bundle cost; use a 'use client' wrapper with navigator.language only when client-side locale detection is needed.

These four Intl APIs cover the vast majority of i18n formatting needs without reaching for a heavy library.

เริ่มต้นได้ฟรี

เรียนรู้ TypeScript ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
22
บทเรียน
88

คำถามที่พบบ่อย

บทเรียน “การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Next.js 15 Fullstack (App Router + Server Actions) ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Next.js 15 Fullstack (App Router + Server Actions) มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา”

ใช้ API ของ Intl เพื่อแสดงวันที่ สกุลเงิน และข้อความพหูพจน์ให้ถูกต้องตามภูมิภาคภาษา คุณปฏิบัติ Next.js 15 Fullstack (App Router + Server Actions) ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Next.js 15 Fullstack (App Router + Server Actions) หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Next.js 15 Fullstack (App Router + Server Actions) บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Next.js 15 Fullstack (App Router + Server Actions) นี้ได้ไหม

ได้ บทเรียน Next.js 15 Fullstack (App Router + Server Actions) ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การตรวจหาภูมิภาคภาษาและการกำหนดเส้นทางย่อยด้วยมิดเดิลแวร์
  2. การโหลดคำแปลฝั่งเซิร์ฟเวอร์และแค็ตตาล็อกข้อความ
  3. ข้อมูลเมตา แผนผังเว็บไซต์ และ hreflang เฉพาะภาษา
  4. การจัดรูปแบบวันที่ ตัวเลข และพหูพจน์ตามภูมิภาคภาษา
← กลับไปที่ Next.js 15 Fullstack (App Router + Server Actions)