تحریریں
22 زبانوں کا Next.js پلیٹ فارم بنانا: GAGA سے سیکھے گئے اسباق
Locale routing، لاطینی اور سیریلیک سربین، Intl formatting، fonts، ترجمے کی CI اور تازہ exchange rates: ایک 22 زبانوں والے Next.js build کے اسباق۔
sigmacode.io انجینئرنگ ٹیم10 منٹ کی قراءت
اس صفحے پر (11)
- ہر locale کو اپنا URL دیں
- دو رسمِ الخط، ایک زبان
- Intl کے ساتھ numbers، currencies اور dates کی formatting
- Fonts اور glyph coverage
- ایک translation workflow جو 22 زبانوں میں ٹکے
- stale values کے بغیر real-time data
- PDF، Excel، CSV اور XML کے لیے ایک source of truth
- کئی locales میں SEO
- Accessibility
- Testing: routes × locales
- کن چیزوں کی منصوبہ بندی کریں
GAGA Menjačnica سربیا کے شہر Novi Sad میں کئی branches رکھنے والا ایک currency exchange اور precious metals dealer ہے۔ ہماری ٹیم نے ان کا پلیٹ فارم menjacnicegaga.rs کو Next.js اور React کے ساتھ Vercel پر بنایا۔ یہ 22 زبانوں میں چلتا ہے، جن میں لاطینی اور سیریلیک دونوں رسمِ الخط میں سربین، جرمن، چینی، روسی، ترکی، یوکرینی، یونانی اور بلغاری شامل ہیں۔ یہ National Bank of Serbia کے reference rates کے ساتھ live خرید و فروخت کے rates دکھاتا ہے، اور اس میں ایک currency converter، پانچ formats میں downloadable rate lists، branch locator اور investment سونے اور چاندی کی guides شامل ہیں۔
بائیس زبانیں اس مرحلے سے آگے ہیں جہاں internationalisation محض ایک feature ہو۔ اس پیمانے پر یہ architecture کو ہی ڈھال دیتی ہے۔ یہ مضمون وہ بتاتا ہے جو ہمارے خیال میں ایسا پلیٹ فارم بناتے وقت اہم ہے۔ یہ ان CTOs، product owners اور frontend engineers کے لیے لکھا گیا ہے جو ایسا ایک منصوبہ بنا رہے ہیں۔ خود پروجیکٹ کے لیے GAGA case study دیکھیں۔
ہر locale کو اپنا URL دیں#
سب سے اہم فیصلہ سب سے پہلے آتا ہے: ہر page کے ہر زبان version کو اپنا مستحکم، crawlable URL چاہیے۔ زبان کو cookie سے یا Accept-Language sniff کر کے نہ بدلیں۔ search engines اسے index نہیں کر سکتے، users اسے share نہیں کر سکتے، اور CDNs اسے صاف طریقے سے cache نہیں کر سکتے۔
ہم path میں locale prefix کی تجویز کرتے ہیں، جیسے /sr/...، /de/... اور /zh/...۔ Next.js App Router میں اس کا مطلب ہے app کے root پر ایک [locale] segment۔ request proxy (proxy.ts، پہلے middleware) browser کی زبان کی بنیاد پر پہلی visit کو کسی معقول default پر redirect کر سکتا ہے۔ اس کے بعد URL واحد source of truth ہے، اور language switcher کو اسی page کی دوسری locale سے link کرنا ہوگا، نہ کہ دوسری locale کے homepage سے۔
اپنے locale identifiers جلدی plan کریں اور ہر جگہ BCP 47 tags استعمال کریں۔ اکیلے سربین کو دو چاہیے: sr-Latn اور sr-Cyrl۔ انہیں URL segments، hreflang values اور Intl locales پر ایک ہی جگہ map کرنا بعد میں بہت سے مسائل بچا دیتا ہے۔
hreflang، x-default اور sitemaps#
ہر page کو اپنے تمام alternates کی فہرست دینی چاہیے، خود اسے بھی شامل کر کے، اور ان users کے لیے x-default شامل کرنا چاہیے جن کی زبان آپ support نہیں کرتے۔ App Router میں metadata API یہ آپ کے لیے کر دیتی ہے:
// app/[locale]/rates/page.tsx
import type { Metadata } from "next";
const BASE = "https://example.com";
const LOCALES = ["sr-Latn", "sr-Cyrl", "en", "de", "zh", "ru"] as const;
const segment = (l: string) => l.toLowerCase(); // "sr-Latn" -> "sr-latn"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params;
const path = "/rates";
const languages: Record<string, string> = Object.fromEntries(
LOCALES.map((l) => [l, `${BASE}/${segment(l)}${path}`])
);
languages["x-default"] = `${BASE}/en${path}`;
return {
alternates: { canonical: `${BASE}/${locale}${path}`, languages },
};
}
اپنی sitemap اسی locale list اور اسی route registry سے بنائیں، ہر entry کے لیے alternates کے ساتھ۔ جس دن hreflang tags اور sitemap entries الگ الگ code paths سے آئیں، وہ بالآخر ایک دوسرے سے مختلف ہوں گے، اور search engines خاموشی سے دونوں پر بھروسا کرنا بند کر دیں گے۔
دو رسمِ الخط، ایک زبان#
سربین لاطینی اور سیریلیک دونوں میں لکھی جاتی ہے، اور بہت سے قارئین کی ایک مضبوط ترجیح ہوتی ہے۔ انہیں display toggle والی ایک locale کے بجائے دو مکمل locales سمجھیں۔ ہر ایک کو اپنا URL، اپنی hreflang value اور اپنا lang attribute ملتا ہے۔
ہر چیز ایک رسمِ الخط میں لکھ کر دوسری کو خودکار طور پر transliterate کرنا پرکشش لگتا ہے۔ جاری متن کے لیے اچھی طرح tested transliteration ایک معقول نقطۂ آغاز ہو سکتی ہے۔ لیکن اسے ہر چیز پر اندھا دھند لاگو نہ کریں:
- Proper nouns اور brands. کمپنی کے نام، product کے نام اور غیر ملکی الفاظ اکثر سیریلیک متن میں بھی اپنی لاطینی شکل رکھتے ہیں۔ ایک mechanical converter انہیں "مددگار" انداز میں کچھ ایسا بنا دے گا جو کسی نے نہیں لکھا۔
- Digraph کی ابہام. لاطینی
nj،ljاورdžعموماً واحد سیریلیک حروف پر map ہوتے ہیں، لیکن ہمیشہ نہیں۔ مرکب الفاظ اور غیر ملکی مستعار الفاظ اس قاعدے کو توڑتے ہیں۔ سیریلیک سے لاطینی deterministic ہے؛ لاطینی سے سیریلیک نہیں۔ - URLs، codes اور identifiers.
EURجیسے currency codes، email addresses، slugs اور interpolation placeholders کے اندر کی ہر چیز کو کبھی transliterate نہیں ہونا چاہیے۔ - تلاش اور ترتیب. سیریلیک page پر users search box میں لاطینی type کر سکتے ہیں۔ compare کرنے سے پہلے دونوں اطراف کو normalise کریں۔
ہماری تجویز: سیریلیک source کو store کریں (deterministic سمت)، یا دو الگ catalogs رکھیں، اور ایک چھوٹی exception list رکھیں جس کی ذمہ داری کسی native speaker کے پاس ہو۔ جو کچھ machine نے بنایا وہ launch سے پہلے review ہونا چاہیے۔
Intl کے ساتھ numbers، currencies اور dates کی formatting#
exchange-rate site پر numbers ہی product ہیں۔ سربین decimal separator کے طور پر comma استعمال کرتی ہے، جرمن ہزاروں کو dot سے گروہ کرتا ہے، اور چینی users پھر مختلف conventions expect کرتے ہیں۔ ہاتھ سے format نہ کریں۔ built-in Intl APIs استعمال کریں اور مکمل locale tag دیں:
const rateFormatter = (locale: string) =>
new Intl.NumberFormat(locale, {
minimumFractionDigits: 4,
maximumFractionDigits: 4,
});
const moneyFormatter = (locale: string, currency: string) =>
new Intl.NumberFormat(locale, { style: "currency", currency });
const asOf = (locale: string, date: Date) =>
new Intl.DateTimeFormat(locale, {
dateStyle: "long",
timeStyle: "short",
timeZone: "Europe/Belgrade",
}).format(date);
rateFormatter("sr-Latn").format(117.1234); // "117,1234"
rateFormatter("de").format(117.1234); // "117,1234"
moneyFormatter("en", "EUR").format(1250); // "€1,250.00"
asOf("sr-Cyrl", new Date()); // e.g. "18. септембар 2026. 10:30" (exact output depends on the ICU version)
چند چیزوں کی پہلے سے منصوبہ بندی کریں۔ time zone کو explicitly set کریں، ورنہ server-rendered pages server کا zone استعمال کرتے ہیں جو عموماً UTC ہوتا ہے۔ formatters ایک بار بنائیں اور دوبارہ استعمال کریں، کیونکہ بڑی table میں بار بار بنانا بڑھتا جاتا ہے۔ اور decimal places کی تعداد data layer اور UI دونوں میں ایک جیسی رکھیں، تاکہ exports اور screen میں اتفاق رہے۔
Fonts اور glyph coverage#
بائیس زبانوں کا مطلب ہے Latin، Latin Extended (سربین، کروشین، ترکی)، Cyrillic (سربین، روسی، یوکرینی، بلغاری)، Greek اور CJK characters۔ کم ہی brand typefaces یہ سب cover کرتے ہیں، اور جو کرتے ہیں وہ بڑے ہوتے ہیں۔
جو اہم ہے:
- typeface منتخب کرنے سے پہلے فی script coverage چیک کریں۔ حقیقی strings سے test کریں، "Lorem ipsum" سے نہیں۔ سربین سیریلیک کے اپنے حروف اور مقامی italic forms ہیں، یوکرینی میں ایسے حروف ہیں جو روسی میں نہیں، اور بلغاری اپنی glyph shapes کو ترجیح دیتا ہے۔
- script کے حساب سے subset اور locale کے حساب سے load کریں۔
next/fontlatin،latin-ext،cyrillicاورgreekجیسے subsets کو support کرتا ہے۔ جرمن page کو سیریلیک glyphs download نہیں کرنے چاہئیں۔ - ہر page پر مکمل CJK font self-host نہ کریں۔ چینی fonts کئی megabytes کے ہو سکتے ہیں۔ CJK کے لیے system font stack، جیسے
"PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif، اکثر درست trade-off ہے۔ - ایک explicit fallback stack بنائیں اور metric-compatible fallbacks set کریں، تاکہ web font load ہونے پر text shift نہ ہو۔
ایک translation workflow جو 22 زبانوں میں ٹکے#
دو تین زبانوں کے لیے ایک spreadsheet اور کچھ نظم کافی ہے۔ 22 کے ساتھ آپ کو ایک pipeline چاہیے۔
مستحکم keys والے message catalogs. فی locale ایک JSON file استعمال کریں، keys کو معنی کے مطابق نام دیں، جیسے rates.table.buy، اور کبھی English text کے مطابق نہیں۔ plurals اور interpolation کے لیے ICU MessageFormat استعمال کریں۔ plural کے rules بہت مختلف ہیں: روسی، یوکرینی اور سربین میں کئی forms ہیں، اور چینی میں کوئی نہیں۔
CI میں key parity. کسی ایک locale میں missing key کسی multilingual site کا سب سے عام bug ہے، اور یہ خودکار طور پر پکڑنے میں سب سے آسان ہے:
// scripts/check-i18n.ts — run in CI, fail the build on drift
import { readdirSync, readFileSync } from "node:fs";
const dir = "messages";
const flatten = (o: Record<string, unknown>, p = ""): string[] =>
Object.entries(o).flatMap(([k, v]) =>
v && typeof v === "object" ? flatten(v as Record<string, unknown>, `${p}${k}.`) : [`${p}${k}`]
);
const load = (f: string) => new Set(flatten(JSON.parse(readFileSync(`${dir}/${f}`, "utf8"))));
const source = load("en.json");
let failed = false;
for (const file of readdirSync(dir).filter((f) => f.endsWith(".json") && f !== "en.json")) {
const keys = load(file);
const missing = [...source].filter((k) => !keys.has(k));
const extra = [...keys].filter((k) => !source.has(k));
if (missing.length || extra.length) {
failed = true;
console.error(`${file}: missing ${missing.length}, extra ${extra.length}`, { missing, extra });
}
}
process.exit(failed ? 1 : 0);
اسی script کو بڑھا کر یہ بھی چیک کریں کہ interpolation placeholders تمام locales میں match کرتے ہیں۔ translate شدہ placeholder name build time پر نہیں بلکہ runtime پر ٹوٹتا ہے۔
AI-assisted ترجمہ، human review کے ساتھ. machine اور LLM ترجمہ اب اچھے پہلے drafts دیتے ہیں، اور 22 زبانوں پر یہ معاشیات بدل دیتا ہے۔ لیکن currency exchange پیسے اور اعتماد سے معاملہ کرتا ہے۔ rate labels، legal notices اور investment guides کو live جانے سے پہلے کسی رواں انسان سے review کرانا چاہیے۔ translators کو context دیں: screenshots، character limits اور "buy rate"، "sell rate" اور "reference rate" جیسی fixed terms کی glossary۔
stale values کے بغیر real-time data#
exchange rates دن بھر بدلتے ہیں۔ جال یہ ہے کہ انہیں static-first Next.js site کے باقی حصے جتنی aggressively cache کیا جائے۔
عام strategies جو ہم تجویز کرتے ہیں:
- shell کو data سے الگ کریں۔ page layout، translations اور guides static ہو سکتے ہیں یا کم revalidate ہو سکتے ہیں۔ rates table کی اپنی چھوٹی revalidation window ہونی چاہیے، یا وہ client پر fetch ہو یا stream ہو۔
- revalidation کو مختصر اور دانستہ رکھیں۔ ایسی window چنیں جو rates کے اصل میں بدلنے کی رفتار سے میل کھائے، اور اسے document کریں۔ جہاں ممکن ہو نئے rates publish ہونے پر on-demand revalidation استعمال کریں، صرف timer پر انحصار کرنے کے بجائے۔
- edge پر احتیاط سے cache کریں۔
stale-while-revalidateکے ساتھ چھوٹاs-maxagepages تیز رکھتا ہے، لیکن یقینی کریں کہ stale window وہ ہو جو business قبول کر سکے۔ - ہمیشہ "as of" timestamp دکھائیں، branch کے time zone میں فی locale format شدہ۔ یہ اس سوال کا ایماندار جواب ہے جو ہر caching system اٹھاتی ہے: یہ کتنا تازہ ہے؟
PDF، Excel، CSV اور XML کے لیے ایک source of truth#
GAGA اپنی rate list PDF، JPG، Excel، CSV اور XML کے طور پر download کے لیے publish کرتی ہے۔ پانچ formats، numbers کے مختلف ہونے کے پانچ مواقع پیدا کرتے ہیں۔
قاعدہ یہ ہے: ہر export اسی normalised data structure سے بنائیں جو on-screen table render کرتی ہے۔ rounding، ordering اور currency metadata اس structure میں رکھیں، ہر exporter میں نہیں۔ پھر ہر format ایک پتلا serializer بن جاتا ہے۔ جن چیزوں کی منصوبہ بندی کرنی ہے:
- CSV کو اعلانیہ delimiter اور encoding چاہیے۔ بہت سے European locales میں Excel semicolon expect کرتا ہے اور BOM کے ساتھ UTF-8 کو بہتر handle کرتا ہے۔
- Excel کو pre-formatted strings کے بجائے number formats والے حقیقی numeric cells ملنے چاہئیں، تاکہ users ان سے حساب کر سکیں۔
- XML کو مستحکم، documented schema چاہیے، کیونکہ کوئی نہ کوئی اس کے ساتھ integrate کرے گا۔
- PDF اور images کو ایسے fonts embed کرنے ہوں گے جو مطلوبہ script cover کریں، جو آپ کو دوبارہ glyph coverage کی طرف لاتا ہے۔
ہر export میں "as of" timestamp بھی رکھیں۔
کئی locales میں SEO#
hreflang اور sitemaps سے آگے:
- فی locale titles، descriptions اور Open Graph metadata کا ترجمہ کریں۔ Greek page پر English metadata نہ چھوڑیں۔
- slugs کو صرف اس صورت میں localise کریں جب آپ انہیں مستحکم رکھ سکیں۔ ایک locale میں بدلا ہوا slug redirects اور ٹوٹے ہوئے hreflang cluster کا باعث بنتا ہے۔
- thin duplicates سے بچیں۔ اگر کسی locale میں جزوی content ہے تو سوچیں کہ کیا اسے ابھی index ہونا چاہیے۔
- canonical URLs کو خود page کی طرف اشارہ کرنا چاہیے، کبھی کسی دوسرے زبان version کی طرف نہیں۔
Accessibility#
ہر locale کے لیے html element پر lang set کریں، مکمل tag استعمال کرتے ہوئے (sr-Latn، sr-Cyrl)، تاکہ screen readers درست voice اور تلفظ چنیں۔ جب page کے اندر دوسری زبان کا کوئی جملہ آئے تو اسے اس کے اپنے lang سے mark کریں۔
چاہے آج آپ کوئی right-to-left زبان support نہ کرتے ہوں، اس کی منصوبہ بندی کریں۔ margin-left کے بجائے margin-inline-start جیسی CSS logical properties استعمال کریں، اور dir کو locale سے derive کریں۔ جب layout left-to-right فرض نہیں کرتا تو بعد میں Arabic شامل کرنا کافی سستا پڑتا ہے۔
Testing: routes × locales#
22 locales کے ساتھ، ایسا bug جو ان میں سے صرف ایک میں ظاہر ہو آسانی سے نظرانداز ہو سکتا ہے۔ ہم ایک automated smoke test کی تجویز کرتے ہیں جو ہر public route اور ہر locale پر چل کر بنیادی باتیں چیک کرے:
- page 200 return کرے اور runtime errors کے بغیر render ہو؛
htmlمیں درستlangہو؛- hreflang alternates مکمل ہوں اور موجود URLs کی طرف اشارہ کریں؛
- کوئی خام message keys (جیسے
rates.table.buy) یا خالی strings ظاہر نہ ہوں؛ - converter اور rate table متوقع format میں numbers render کریں۔
سب سے لمبی زبانوں کے لیے visual snapshots شامل کریں۔ جرمن اور یونانی labels اکثر ایسے layouts توڑ دیتے ہیں جو English میں ٹھیک لگ رہے ہوتے ہیں۔
کن چیزوں کی منصوبہ بندی کریں#
اگر آپ ایک multilingual Next.js پلیٹ فارم شروع کر رہے ہیں تو یہ وہ فیصلے ہیں جو پہلے ہی دن کرنے ہیں: locale-prefixed URL strategy، locales کی ایک registry جو routing، metadata اور sitemaps کو چلائے، scripts اور transliteration کی واضح policy، تمام formatting کے لیے Intl، فی script font plan، translation parity کے لیے CI checks، live data کے لیے واضح freshness policy، اور ہر export کے لیے ایک source of truth۔
اس میں کوئی چیز غیر معمولی نہیں، لیکن اسے design میں شامل کرنا بعد میں retrofit کرنے سے کہیں سستا ہے۔ ہمارے کام کی قیادت 20+ سال کے تجربے رکھنے والا tech lead کرتا ہے، اور یہی وہ groundwork ہے جس پر ہم Web & Platforms میں توجہ دیتے ہیں۔ اگر آپ کچھ ایسا ہی plan کر رہے ہیں تو رابطہ کریں۔