
13 พฤษภาคม 2566
เพิ่มภาษาไทย
เรามาเพิ่มภาษาไทยให้กับบล็อกดูกัน
เนื่องจากผู้ชมจากช่อง YouTube ของผมส่วนใหญ่เป็นคนไทย ผมจึงควรที่จะเพิ่มภาษาไทยให้กับบล็อกของผมด้วย แต่ผมจะใช้วิธีเขียนเป็นภาษาอังกฤษก่อน แล้วค่อยๆ แปลเป็นภาษาไทยตามมาทีหลังเอาครับ (แปลกเนาะเป็นคนไทยแต่ดันเขียนเป็นภาษาอังกฤษก่อน 555)
ผมลองทำตามไกด์เพิ่ม i18n ให้กับ Astro จากคู่มือการใช้งานดูก็พบว่าค่อนข้างง่ายแต่อาจจะต้องมีการปรับแต่งบางส่วนให้เข้ากับบล็อกของผมครับ
แปลเนื้อหาบล็อก
-
เนื่องจากผมได้มีการวางแผนไว้ตั้งแต่ก่อนทำบล็อกนี้แล้วว่าจะทำสองภาษา ไฟล์ต่างๆ จึงอยู่ในแฟ้ม
en
อยู่แล้ว สิ่งที่ผมต้องทำจึงมีเพียงสร้างแฟ้มth
ในsrc/pages
แล้วคัดลอกไฟล์ทั้งหมดจากแฟ้มen
ไปไว้ในแฟ้มth
-
สร้างไฟล์
src/pages/index.astro
(site root) และใส่ meta redirect ไปที่แฟ้มen
---
---
<meta http-equiv="refresh" content="0;url=/en/" />
ผมอาจจะเก็บภาษาที่ผู้ใช้เลือกไว้ใน LocalStorage แล้ว redirect ไปที่ภาษานั้นๆ ในอนาคต
- หลังจากอ่านคู่มืออย่างละเอียด ผมพบว่า 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
- สร้างไฟล์
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,
};
- ใช้ 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/>
- อัพเดทระบบสร้างรายการโพสต์ในหน้าหลักให้ใช้
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>
แปลอินเตอร์เฟซอื่นๆ ในเว็บไซต์
- สร้างไฟล์
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;
- สร้างไฟล์
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 ออกมาเพื่อใช้งานในไฟล์อื่นๆ ด้วย
- ใช้ฟังก์ชันช่วยในการแปลงภาษาในไฟล์ต่อไปนี้:
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' },
ให้ผู้ใช้สามารถเปลี่ยนภาษาได้
- สร้าง 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>
- เพิ่ม component ที่สร้างไว้ใน header
src/components/Header.svelte
<script lang="ts">
import LanguagePicker from '../components/LanguagePicker.svelte';
</script>
<nav>
...
+ <LanguagePicker {url} />
...
</nav>
สรุปท้าย
สำหรับวันนี้ก็คงเท่านี้ก่อนนะครับ ผมจะพยายามเขียนบล็อกเพิ่มเติมในอนาคต และจะพยายามแปลเป็นภาษาไทยด้วย แต่ตอนนี้ได้เวลาเล่น Path of Exile แล้ว ถ้าใครสนใจก็ตามไปดูผมเล่นสดได้ที่ YouTube นะคร้าบ