Blog ng 18ways
Paano magdagdag ng multilingual i18n at localisation sa isang umiiral nang Next.js app nang hindi sinasaktan ang SEO o muling itinatayo mula sa simula.
Ang I18n (internationalisation), l10n (localisation), multilingual support… sa kahit paano mo pa sabihin, sa bandang huli kailangan mong i-update ang Next.js app mo para suportahan ang higit sa isang wika.
Ang hirap ay hindi ang pagsasalin ng isang pangungusap. Ang mahirap ay ang pagdagdag ng maraming wika sa isang tunay na Next.js app nang hindi nasisira ang SEO, nagugulo ang codebase mo, o lumilikha ng problema sa maintenance para sa sarili mo.
Kung gusto mong dumiretso sa code, tingnan ang mga halimbawa ng GitHub ng 18ways-next.
Bago ang lahat, kailangan muna nating i-install ang mga package natin. Ise-setup natin ang mga 18ways lib, pero mahusay ding gumana ang mga tool tulad ng i18next kung simple lang ang proyekto mo at static content lang ang laman nito.
npm install @18ways/next @18ways/reactGumawa ng iyong config file:
// 18ways.config.js
module.exports = {
apiKey: 'pk_dummy_demo_token',
baseLocale: 'en-GB',
router: 'app', // 'app', or 'path' depending on which Next.js router you are using
};Ibalot ang iyong Next.js config:
// next.config.js
const { withWays } = require('@18ways/next/config');
const nextConfig = {
/*
* your normal Next.js config here
*/
};
module.exports = withWays(nextConfig);Magdagdag ng root proxy para ma-redirect ang / sa tamang locale:
// proxy.js
export { default, config } from '@18ways/next/proxy';Pagkatapos, magdagdag ng localized layout:
// app/layout.jsx
import './styles.css';
import { WaysRoot } from '@18ways/next/server';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className="next-demo-body">
<WaysRoot>{children}</WaysRoot>
</body>
</html>
);
}At tapos na tayo! Ngayon ay mayroon ka nang:
Maaari mo nang isalin ang unang page mo. Ang mga tool tulad ng i18next ay mangangailangang hatiin mo ang lahat ng text mo sa mga translation key, at saka i-reference ang mga ito sa code mo. Kung ano mismo ang itsura nito ay nakadepende sa library na napili mo.
Kung gumagamit ka ng 18ways, puwede mo lang balutin ang text na gusto mong isalin sa isang <T> component:
// src/components/MyExampleComponent.jsx
import { useT, T } from '@18ways/react';
export default function MyExampleComponent() {
const t = useT();
return (
<div>
<T>Hello world!</T>
<img
src="https://example.com/image.png"
alt={t('Example image')}
/>
</div>
);
}Malamang na gusto mong payagang magpalit ng wika ang mga user:
// src/components/Footer.jsx
import { T, LanguageSwitcher } from '@18ways/react';
export default function Footer() {
return (
<footer>
<T>My footer content</T>
<LanguageSwitcher />
</footer>
);
}Kapag ayos na ang mga nakahandang bahagi, may ilang paraan kung paano kadalasang nagkakamali ang mga proyektong ito.
Ang server-side rendering (SSR) ang nagbibigay-daan para ma-pre-render ng iyong Next.js app ang HTML sa server. Mahalaga ito para sa SEO at para hindi makakita ang user ng biglang paglabas ng maling content o maling wika.
Kung gumagamit ka ng library gaya ng i18next, kailangan mong maging sobrang maingat na ang mga translation mo ay nae-load at napupunan sa server-side render. Masusubukan mo ito sa pamamagitan ng pagtingin sa view-source: ng page mo, hal. view-source:http://localhost:3000/. Dapat mo rin itong tingnan sa production, para masigurong gumagana rin ito sa production build mo.
Kung gumagamit ka ng 18ways, huwag mo nang alalahanin ito. Lahat ay inaasikaso na para sa iyo.
Kung gumagamit ka ng 18ways, hindi mo na kailangang mag-alala tungkol dito. Hindi kailangan ng 18ways ng translation keys, puwede mong iwan lang sa lugar ang text mo gaya ng normal.
Maraming i18n system ang nangangailangan na hati-hatiin mo ang code mo sa mga translation key. Mahalaga na maging maingat sa kung paano mo pinapangalanan ang mga key na ito.
Malabo ang mga bad key:
// bad-keys.en-GB.js
module.exports = {
title: 'Continue',
button: 'Pay now',
label: 'Home',
};Halos walang sinasabi ang mga key na iyon sa mga tagasalin at developer tungkol sa kung saan lumalabas ang teksto.
Mas mainam na mga key ang may konteksto:
// better-keys.en-GB.js
module.exports = {
'checkout.payment.primaryButton': 'Pay now',
'checkout.payment.stepTitle': 'Complete your payment',
'account.sidebar.homeLink': 'Home',
};Iwasan din ang dinamikong paggawa ng mga key:
// bad-dynamic-keys.js
const key = `checkout.${status}.${buttonType}`;
const translatedText = t(key);Masisira nito ang mga IDE tool na sumusubok gawing mas hindi mabigat ang mga translation key. Magiging napakahirap din nitong maghanap at maglinis ng lumang translation key.
// better-dynamic-keys.js
const keyMap = {
success: {
primary: t('checkout.success.primary'),
default: t('checkout.success.default'),
},
error: {
primary: t('checkout.error.primary'),
default: t('checkout.error.default'),
},
};
const translatedText = keyMap[status][buttonType];Hindi ito kasing-DRY, pero ito ang pinakamahusay na paraan para mapigilan ang mga translation key na maging hindi na kayang pamahalaan.
Mas maganda pa ang gumamit ng tool gaya ng 18ways, dahil hindi mo na kailangang gumamit ng translation keys kailanman:
// app/[lang]/checkout/page.jsx
<T>Pay now</T>Ang locale detection ay ang pagpapasya kung anong wika ang dapat makita ng user bago pa man siya manu-manong lumipat.
Karaniwan, kabilang dito ang kombinasyon ng:
Accept-Language ng browserSa plain Next.js middleware, madalas ganito ang itsura nito:
// middleware.js
/**
* You don't need any of this if you're using 18ways
*/
import { NextResponse } from 'next/server';
const acceptedLocales = ['en-GB', 'fr-FR'];
export function middleware(request) {
const savedLocale =
request.cookies.get('preferred-locale')?.value;
const browserLocale =
request.headers
.get('accept-language')
?.split(',')[0] || 'en-GB';
const locale = acceptedLocales.includes(savedLocale)
? savedLocale
: acceptedLocales.includes(browserLocale)
? browserLocale
: 'en-GB';
if (request.nextUrl.pathname === '/') {
return NextResponse.redirect(
new URL(`/${locale}`, request.url)
);
}
return NextResponse.next();
}May ilang library na may mga helper para mas mapadali ito, sa iba’t ibang antas. Sa 18ways, ang paunang pag-detect at ang redirect layer ay inaasikaso na para sa iyo.
Iba-iba ang pag-format ng mga date, number, at pera sa bawat locale.
Halimbawa:
04/05/2026 ay puwedeng mangahulugang ika-4 ng Mayo o ika-5 ng Abril€1,999.00 at 1.999,00 € ay parehong wasto, depende sa localeSa raw JavaScript, ikaw ang bahala rito gamit ang Intl:
// formatting-dates-and-currency.js
const myLocale = getCurrentLocale(); // depends on your lib
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const dateLabel = new Intl.DateTimeFormat('fr-FR', {
dateStyle: 'long',
}).format(someTimestamp);
const someMoney = {
amount: 1999,
currency: 'EUR',
};
const moneyLabel = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: someMoney.currency,
}).format(someMoney.amount);
const translatedText = t(
'my.translation.key',
{ dateLabel, moneyLabel }
);Kung gumagamit ka ng 18ways, ito na ang bahalang gumawa nito para sa iyo:
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const someMoney = {
amount: 1999,
currency: 'EUR',
};
<T>My text with {{ someTimestamp }} and {{ someMoney }}</T>Huwag gumawa ng isinaling UI sa pamamagitan ng pagsasama-sama ng mga pira-piraso:
// bad-string-joining.jsx
const clickHereText = t('click.here');
const toGetStartedText = t('to.get.started')
<p><a href="#">{clickHereText}</a> {toGetStartedText}.</p>Masisira ito sa maraming wika.
Maaaring sabihin mo sa Ingles na “👉👉Click here👈👈 to get started”, pero sa French mas natural ang “Pour commencer, 👉👉cliquez ici👈👈”. Ang ilang wika gaya ng Japanese ay mangangailangan pa nga ng mga salita bago at pagkatapos tulad ng “始めるには👉👉こちら👈👈をクリックしてください”.
Maaaring magbago ang estruktura ng pangungusap, kaya mas mahirap isalin nang maayos kapag hinahati-hati ito sa mga piraso.
Mas mainam magsalin ng buong pangungusap dahil mas natural na naiaayos ng mga tagasalin ang mga salita at nakikita nila ang kahulugan bilang isang buong yunit.
Kung nagsasalin ka ng JSX na ganito:
// rich-text-message.jsx
<p>
Something <strong>that we want to be bold</strong>
</p>kailangan mong sumangguni sa i18n library mo kung paano ito pamahalaan, dahil ibang-iba ang paghawak nito depende sa library.
Kung gumagamit ka ng 18ways, puwede mo na lang isalin ang buong JSX block gaya ng normal.
// rich-text-message.jsx
<p>
<T>Something <strong>that we want to be bold</strong></T>
</p>Pinapayagan ka ng variables na panatilihing buo ang pangungusap habang naglalagay pa rin ng mga value sa runtime.
Sinusuportahan ito ng karamihan sa mga i18n library sa ilang paraan. Sa 18ways:
// app/[lang]/page.jsx
<T>Hello {{ name: 'Ada' }}</T>Gumagana nang maayos ang pattern na iyon para sa:
Nanatiling madaling basahin ang pangungusap, at nananatiling malinaw ang halaga.
Kung kailangan mong maisalin din ang mismong variable, siguraduhing ilagay iyon sa loob ng t(...):
const animal = t('dog');
<T>Favourite animal: {{ animal }}</T>Nagkakaiba-iba ang plural rules ayon sa wika. Ang English ay may isang plural form lang (1 year, 2 years, atbp.). Ang Polish ay may maraming plural form (1 rok, 2 lata, 5 lat). Ang Japanese ay walang plural form (1 年, 2 年, 3 年).
Hinahayaan ng karamihan sa mga i18n library na magtakda ka ng syntax na kahalintulad ng ICU para sa paghawak ng mga plural:
// app/[lang]/page.jsx
<T>
{{
unreadCount,
format:
'plural, =0{No unread messages} =1{One unread message} other{{unreadCount} unread messages}',
}}
</T>Gagana ang nasa itaas sa 18ways, pero sa karamihan ng kaso puwede mo ring gawin lang ito:
// app/[lang]/page.jsx
<T>{{ unreadCount }} unread messages</T>Hahawakan ng 18ways ang mga plural para sa iyo!
Hindi kailangang maging isang rewrite project ang pagdaragdag ng maraming wika sa isang Next.js app.
Kung hindi ka gumagamit ng solusyon tulad ng 18ways — siguraduhing tama ang routing at SSR mo, saka isa-isang ayusin ang paglipat ng copy mo sa mga translation key, at iwasan ang mga karaniwang i18n pitfall.
Kung gumagamit ka ng 18ways, lahat ng ito ay awtomatikong aasikasuhin para sa iyo, at kailangan mo na lang simulan ang paglalagay ng text mo sa mga <T> block!