Thumbnail of Thai flag.

13 พฤษภาคม 2566

เพิ่มภาษาไทย

เรามาเพิ่มภาษาไทยให้กับบล็อกดูกัน


เนื่องจากผู้ชมจากช่อง YouTube ของผมส่วนใหญ่เป็นคนไทย ผมจึงควรที่จะเพิ่มภาษาไทยให้กับบล็อกของผมด้วย แต่ผมจะใช้วิธีเขียนเป็นภาษาอังกฤษก่อน แล้วค่อยๆ แปลเป็นภาษาไทยตามมาทีหลังเอาครับ (แปลกเนาะเป็นคนไทยแต่ดันเขียนเป็นภาษาอังกฤษก่อน 555)

ผมลองทำตามไกด์เพิ่ม i18n ให้กับ Astro จากคู่มือการใช้งานดูก็พบว่าค่อนข้างง่ายแต่อาจจะต้องมีการปรับแต่งบางส่วนให้เข้ากับบล็อกของผมครับ

แปลเนื้อหาบล็อก

  1. เนื่องจากผมได้มีการวางแผนไว้ตั้งแต่ก่อนทำบล็อกนี้แล้วว่าจะทำสองภาษา ไฟล์ต่างๆ จึงอยู่ในแฟ้ม en อยู่แล้ว สิ่งที่ผมต้องทำจึงมีเพียงสร้างแฟ้ม th ใน src/pages แล้วคัดลอกไฟล์ทั้งหมดจากแฟ้ม en ไปไว้ในแฟ้ม th

  2. สร้างไฟล์ src/pages/index.astro (site root) และใส่ meta redirect ไปที่แฟ้ม en

---
---
<meta http-equiv="refresh" content="0;url=/en/" />

ผมอาจจะเก็บภาษาที่ผู้ใช้เลือกไว้ใน LocalStorage แล้ว redirect ไปที่ภาษานั้นๆ ในอนาคต

  1. หลังจากอ่านคู่มืออย่างละเอียด ผมพบว่า Astro มี Content Collections ซึ่งไม่ได้ถูกกล่าวถึงเลยในคู่มือที่ผมใช้สร้างบล็อกนี้ แต่ลองอ่านดูก็พบว่ามันเหมาะที่จะนำมาใช้งานและมันยังช่วยให้เราสามารถใช้ TypeScript ในการเขียนโค้ดได้ง่ายขึ้นด้วย ผมจึงทำการย้ายไฟล์เนื้อหาบล็อกทั้งหมดไปไว้ใน src/content/blog แทน

ตอนนี้โครงสร้างของ src/content/blog จะมีลักษณะดังนี้:

- src
  - content
    - blog
      - en
        - post-1.md
        - post-2.md
      - th
        - post-1.md
        - post-2.md
  1. สร้างไฟล์ src/content/config.ts และ export collection สำหรับแต่ละประเภทของเนื้อหาออกมา
import { defineCollection, z } from "astro:content";

const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string(),
    author: z.string().default("Anonymous"),
    lang: z.enum(["en", "th"]),
    tags: z.array(z.string()),
    pubDate: z.date(),
    image: z
      .object({
        src: z.string(),
        alt: z.string(),
      })
      .optional(),
    isDraft: z.boolean(),
  }),
});

export const collections = {
  blog: blogCollection,
};
  1. ใช้ dynamic routes เพื่อสร้างเนื้อหาของแต่ละโพสต์

src/pages/[lang]/blog/[…slug].astro

---
import { getCollection } from 'astro:content'

export async function getStaticPaths() {
  const pages = await getCollection('blog')

  const paths = pages.map(page => {
    const [lang, ...slug] = page.slug.split('/');
    return { params: { lang, slug: slug.join('/') || undefined }, props: page }
  })

  return paths;
}

const { lang, slug } = Astro.params;
const page = Astro.props;
const formattedDate = page.data.pubDate.toLocaleString(lang);

const { Content } = await page.render();
---
<Content/>
  1. อัพเดทระบบสร้างรายการโพสต์ในหน้าหลักให้ใช้ getCollection('blog') แทน Astro.glob<Frontmatter>("./posts/*.md") ของเดิม และผมยังย้ายไฟล์ไปไว้ใน src/pages/[lang] ด้วย

src/pages/[lang]/index.astro

---
import BaseLayout from '../../layouts/BaseLayout.astro';
import BlogCard from '../../components/BlogCard.svelte';
import { getCollection } from 'astro:content';
import { languages } from '../../i18n/ui';

export async function getStaticPaths() {
  let paths = [];
  for (const lang of Object.keys(languages)) {
    paths.push({
      params: {
        lang,
      },
    });
  }

  return paths;
}

const { lang } = Astro.params;
const blogPosts = await getCollection('blog');

const pageTitle = "XiaZ.TV";
---

<BaseLayout pageTitle={pageTitle} description="XiaZ.TV -- Blog">
  <div class="max-w-7xl mx-auto px-8 py-4">
    <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
      {
        blogPosts.map(post => {
          const [_lang, ...slug] = post.slug.split('/');
          if (_lang !== lang) {
            return;
          }
          const url = "/" + lang + "/blog/" + slug.join('/');
          return <BlogCard
            title={post.data.title}
            description={post.data.description}
            url={url}
            imageSrc={post.data.image.src}
            imageAlt={post.data.image.alt}
            pubDate={post.data.pubDate}
          />
        })
      }
    </div>
  </div>
</BaseLayout>

แปลอินเตอร์เฟซอื่นๆ ในเว็บไซต์

  1. สร้างไฟล์ src/i18n/ui.ts และใส่ข้อความที่ต้องการแปลเข้าไป
export const languages = {
  en: "English",
  th: "ไทย",
};

export const defaultLang = "en";

export const ui = {
  en: {
    "nav.home": "Home",
    "nav.build": "Build",
    "nav.tool": "Tool",
    "nav.community": "Community",
    "nav.about": "About",
  },
  th: {
    "nav.home": "หน้าหลัก",
    "nav.build": "บิลด์",
    "nav.tool": "เครื่องมือ",
    "nav.community": "ชุมชน",
    "nav.about": "เกี่ยวกับ",
  },
} as const;
  1. สร้างไฟล์ src/i18n/utils.ts และสร้างฟังก์ชันสองฟังก์ชัน เพื่อใช้ในการแปลงภาษา: ฟังก์ชันแรกใช้ในการตรวจสอบภาษาของหน้าปัจจุบัน และฟังก์ชันที่สองใช้ในการแปลงข้อความต่างๆ ในอินเตอร์เฟซ
import { languages, ui, defaultLang } from "./ui";

export function getLangFromUrl(url: URL) {
  const [, lang] = url.pathname.split("/");
  if (lang in ui) return lang as keyof typeof ui;
  return defaultLang;
}

export function useTranslations(lang: keyof typeof ui) {
  return function t(key: keyof (typeof ui)[typeof defaultLang]) {
    return ui[lang][key] || ui[defaultLang][key];
  };
}

export type Language = keyof typeof languages;

หมายเหตุ: ผมยัง export Language type ออกมาเพื่อใช้งานในไฟล์อื่นๆ ด้วย

  1. ใช้ฟังก์ชันช่วยในการแปลงภาษาในไฟล์ต่อไปนี้:

src/layouts/BaseLayout.astro

---
+ import { getLangFromUrl } from '../i18n/utils';
+ const lang = getLangFromUrl(Astro.url);
---
- <html lang="en" class="bg-stone-950">
+ <html lang={lang} class="bg-stone-950">
- <Header currentPage={currentPage} client:load />
+ <Header currentPage={currentPage} lang={lang} client:load />

src/components/Header.svelte

<script lang="ts">
+ import { type Language, useTranslations } from '../i18n/utils';
+ export let lang: Language = 'en'
+ const t = useTranslations(lang)

const routes = [
- { name: 'Build', path: 'build' },
+ { name: t('nav.build'), path: 'build' },

ให้ผู้ใช้สามารถเปลี่ยนภาษาได้

  1. สร้าง component สำหรับแสดงลิงค์เปลี่ยนภาษา

src/components/LanguagePicker.svelte

<script lang="ts">
  import { languages } from '../i18n/ui';
  export let url: URL | null = null

  const [, langFromUrl, ...rest] = url?.pathname.split('/') ?? []
  const urlWithoutLang = rest.join('/')
</script>

<div>
  {#each Object.entries(languages) as [_lang, label]}
    <a
      class=""
      href={"/" + _lang + "/" + urlWithoutLang}
    >
      <img src="/flags/{_lang}.svg" alt={label} class="inline-block w-6 h-4 shadow hover:ring-1">
    </a>
  {/each}
</div>
  1. เพิ่ม component ที่สร้างไว้ใน header

src/components/Header.svelte

<script lang="ts">
  import LanguagePicker from '../components/LanguagePicker.svelte';
</script>

<nav>
...
+ <LanguagePicker {url} />
...
</nav>

สรุปท้าย

สำหรับวันนี้ก็คงเท่านี้ก่อนนะครับ ผมจะพยายามเขียนบล็อกเพิ่มเติมในอนาคต และจะพยายามแปลเป็นภาษาไทยด้วย แต่ตอนนี้ได้เวลาเล่น Path of Exile แล้ว ถ้าใครสนใจก็ตามไปดูผมเล่นสดได้ที่ YouTube นะคร้าบ

No comments yet.