xiaohongshu
xiaohongshu
Designing software applications to adapt to different languages and regions without requiring engineering changes, using translation keys, pluralization, and locale-aware formatting.
Full skill instructions
Current Level: Intermediate
Domain: Internationalization / Frontend
Internationalization (i18n) is the process of designing software applications to adapt to different languages and regions without requiring engineering changes. Effective i18n uses translation keys, pluralization rules, date/number formatting, and RTL support to create applications that work globally.
| Aspect | i18n | l10n |
|---|---|---|
| Full Name | Internationalization | Localization |
| Focus | Making app translatable | Translating to specific language |
| When | During development | After i18n is complete |
| Who | Developers | Translators |
| Output | Code with translation keys | Translated content |
┌─────────────────────────────────────────────────────────────────┐
│ i18n Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Source │───▶│ Translation │───▶│ Display │ │
│ │ Code │ │ Files │ │ Layer │ │
│ │ │ │ │ │ │ │
│ │ t('key') │ │ { "key": │ │ Hello World │ │
│ │ │ │ "value" │ │ │ │
│ │ │ │ } │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
language-COUNTRY
| Locale | Language | Country |
|---|---|---|
en-US | English | United States |
en-GB | English | United Kingdom |
th-TH | Thai | Thailand |
pt-BR | Portuguese | Brazil |
ja-JP | Japanese | Japan |
const SUPPORTED_LOCALES = [
'en-US',
'th-TH',
'ja-JP',
'pt-BR',
'de-DE',
'fr-FR',
'es-ES',
'zh-CN'
];
{
"common": {
"welcome": "Welcome",
"login": "Login",
"logout": "Logout"
},
"auth": {
"email": "Email",
"password": "Password",
"forgotPassword": "Forgot Password?",
"signup": "Sign Up"
},
"errors": {
"required": "This field is required",
"invalidEmail": "Invalid email address",
"wrongPassword": "Incorrect password"
}
}
{
"user": {
"profile": {
"title": "Profile",
"name": "Name",
"email": "Email"
},
"settings": {
"title": "Settings",
"language": "Language",
"theme": "Theme"
}
}
}
{
"items": {
"zero": "No items",
"one": "One item",
"other": "{{count}} items"
}
}
| Library | Description |
|---|---|
| react-i18next | Most popular, feature-rich |
| react-intl | Format.js-based, good for complex formatting |
| react-intl-universal | Lightweight, simple |
| Library | Description |
|---|---|
| vue-i18n | Official Vue plugin |
| vue-intl | Alternative plugin |
| Library | Description |
|---|---|
| @angular/localize | Official Angular i18n |
| ngx-translate | Community favorite |
| Library | Description |
|---|---|
| i18next | Framework-agnostic |
| i18n-node | Simple Node.js i18n |
| Library | Description |
|---|---|
| Babel | Python i18n library |
| gettext | GNU gettext for Python |
| Library | Description |
|---|---|
| ResourceBundle | Built-in Java i18n |
| MessageFormat | ICU MessageFormat |
npm install i18next react-i18next
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const resources = {
en: {
translation: {
welcome: 'Welcome',
login: 'Login'
}
},
th: {
translation: {
welcome: 'ยินดีต้อนรับ',
login: 'เข้าสู่ระบบ'
}
}
};
i18n.use(initReactI18next({
resources,
lng: 'en', // Default language
fallbackLng: 'en',
interpolation: { escapeValue: false },
react: {
useSuspense: false
}
}));
export default i18n;
import { useTranslation } from 'react-i18next';
function Welcome() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<button>{t('login')}</button>
</div>
);
}
const { t } = useTranslation();
function Greeting({ name }) {
return <p>{t('greeting', { name })}</p>;
}
// Translation: "greeting": "Hello, {{name}}"
const { t } = useTranslation();
function ItemCount({ count }) {
return <p>{t('items', { count })}</p>;
}
// Translation:
// "items_zero": "No items",
// "items_one": "One item",
// "items_other": "{{count}} items"
| Convention | Example | Why |
|---|---|---|
| Descriptive | user.profile.title | Self-documenting |
| Hierarchical | auth.login.email | Organized |
| Consistent | button.submit | Predictable |
| Avoid | btn1, txt2 | Unclear |
{
"common": {
"actions": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete"
}
},
"auth": {
"login": {
"title": "Login",
"email": "Email",
"password": "Password",
"button": "Login"
},
"signup": {
"title": "Sign Up",
"name": "Name",
"email": "Email",
"password": "Password",
"button": "Sign Up"
}
},
"dashboard": {
"title": "Dashboard",
"overview": "Overview",
"analytics": "Analytics",
"settings": "Settings"
}
}
const { t } = useTranslation();
function Welcome() {
return (
<div>
<h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
</div>
);
}
function detectBrowserLanguage() {
return navigator.language || navigator.userLanguage;
}
// Returns: "en-US", "th-TH", etc.
// Get from localStorage
function getUserLanguage() {
return localStorage.getItem('userLanguage') || 'en-US';
}
// Set user preference
function setUserLanguage(language) {
localStorage.setItem('userLanguage', language);
i18n.changeLanguage(language);
}
function getLanguageFromURL() {
const params = new URLSearchParams(window.location.search);
return params.get('lang') || getUserLanguage();
}
// Usage: ?lang=th-TH
function getLanguageFromSubdomain() {
const subdomain = window.location.hostname.split('.')[0];
const languageMap = {
'en': 'en-US',
'th': 'th-TH',
'ja': 'ja-JP'
};
return languageMap[subdomain] || 'en-US';
}
// Usage: en.example.com, th.example.com
URL Parameter → User Preference → Browser Language → Default
function getLanguage() {
// 1. Check URL parameter
const urlLang = getLanguageFromURL();
if (urlLang && SUPPORTED_LOCALES.includes(urlLang)) {
return urlLang;
}
// 2. Check user preference
const userLang = getUserLanguage();
if (userLang && SUPPORTED_LOCALES.includes(userLang)) {
return userLang;
}
// 3. Check browser language
const browserLang = detectBrowserLanguage();
if (browserLang && SUPPORTED_LOCALES.includes(browserLang)) {
return browserLang;
}
// 4. Default
return 'en-US';
}
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const { changeLanguage } = i18n;
const handleLanguageChange = (language) => {
changeLanguage(language);
setUserLanguage(language);
};
return (
<select value={i18n.language} onChange={(e) => handleLanguageChange(e.target.value)}>
<option value="en-US">English</option>
<option value="th-TH">ไทย</option>
<option value="ja-JP">日本語</option>
<option value="pt-BR">Português</option>
</select>
);
}
function setUserLanguage(language) {
localStorage.setItem('userLanguage', language);
// Also send to server if user is logged in
if (isLoggedIn()) {
fetch('/api/user/language', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ language })
});
}
}
function changeLanguage(language) {
i18n.changeLanguage(language);
setUserLanguage(language);
// Optional: Reload page to apply new language
// window.location.reload();
}
| Language | Direction |
|---|---|
| Arabic | RTL |
| Hebrew | RTL |
| Persian | RTL |
| Urdu | RTL |
import { useTranslation } from 'react-i18next';
function App() {
const { i18n } = useTranslation();
const isRTL = ['ar', 'he', 'fa', 'ur'].includes(i18n.language.split('-')[0]);
return (
<html lang={i18n.language} dir={isRTL ? 'rtl' : 'ltr'}>
{/* App content */}
</html>
);
}
/* Logical properties instead of physical properties */
.container {
margin-inline-start: 20px; /* margin-left for LTR, margin-right for RTL */
padding-inline-end: 20px; /* padding-right for LTR, padding-left for RTL */
text-align: start; /* text-align-left for LTR, text-align-right for RTL */
}
/* Flexbox */
.flex-container {
flex-direction: row; /* Works with logical properties */
gap: 20px;
}
/* Flip icons for RTL */
[dir="rtl"] .icon-arrow {
transform: scaleX(-1);
}
function formatDate(date, locale) {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
}
// Usage
const date = new Date('2024-01-15');
console.log(formatDate(date, 'en-US')); // January 15, 2024
console.log(formatDate(date, 'th-TH')); // 15 มกราคม 2567
function formatNumber(number, locale) {
return new Intl.NumberFormat(locale).format(number);
}
// Usage
console.log(formatNumber(1234.56, 'en-US')); // 1,234.56
console.log(formatNumber(1234.56, 'th-TH')); // 1,234.56
console.log(formatNumber(1234.56, 'de-DE')); // 1.234,56
function formatCurrency(amount, currency, locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
}
// Usage
console.log(formatCurrency(99.99, 'USD', 'en-US')); // $99.99
console.log(formatCurrency(99.99, 'THB', 'th-TH')); // ฿99.99
console.log(formatCurrency(99.99, 'EUR', 'de-DE')); // 99,99 €
// Extract translation keys
const translations = {
en: require('./locales/en.json'),
th: require('./locales/th.json')
};
// Export to CSV
function exportToCSV(translations, language) {
const keys = Object.keys(translations[language].translation);
const values = keys.map(key => translations[language].translation[key]);
const csv = [
keys.join(','),
values.join(',')
].join('\n');
// Download CSV
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${language}.csv`;
a.click();
}
| Platform | Features | Pricing |
|---|---|---|
| Lokalise | Crowdsourced, version control | $$ |
| Crowdin | Crowdsourced, integrations | $$ |
| Phrase | In-context editor, integrations | $$$ |
| POEditor | Simple, affordable | $ |
┌─────────────────────────────────────────────────────────────────┐
│ Git-Based Translation Workflow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Developer ──▶ 2. Export Keys ──▶ 3. Upload to │
│ Extracts to CSV Platform │
│ Keys │
│ │
│ 4. Translator ──▶ 5. Download │
│ Translates Translations │
│ │
│ 6. Developer ──▶ 7. Import to │
│ Imports Project │
│ │
│ 8. Commit ──▶ 9. Deploy │
│ Changes Translations │
└─────────────────────────────────────────────────────────────────┘
const { t } = useTranslation();
function Welcome() {
return (
<div>
<h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
{/* Shows 'welcome' if translation missing */}
</div>
);
}
const { t } = useTranslation();
function Welcome() {
return (
<div>
<h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
{/* Shows 'welcome' if translation missing */}
</div>
);
}
// Configure fallback language
i18n.use(initReactI18next({
fallbackLng: 'en', // Fallback to English
// ... other config
}));
function missingKeyHandler(lng, ns, key) {
console.warn(`Missing translation: ${lng}.${ns}.${key}`);
// Send to analytics
if (window.analytics) {
window.analytics.track('missing_translation', {
language: lng,
namespace: ns,
key: key
});
}
return key; // Return key as fallback
}
i18n.use(initReactI18next({
missingKeyHandler,
// ... other config
}));
/en/ (English)
/th/ (Thai)
/ja/ (Japanese)
/pt-BR/ (Portuguese)
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="th" href="https://example.com/th/" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />
function LanguageSelector() {
const languages = [
{ code: 'en', name: 'English', url: '/en/' },
{ code: 'th', name: 'ไทย', url: '/th/' },
{ code: 'ja', name: '日本語', url: '/ja/' }
];
return (
<nav aria-label="Language">
{languages.map(lang => (
<a key={lang.code} href={lang.url}>
{lang.name}
</a>
))}
</nav>
);
}
import { useTranslation } from 'react-i18next';
function MetaTags() {
const { t } = useTranslation();
return (
<Helmet>
<title>{t('meta.title')}</title>
<meta name="description" content={t('meta.description')} />
<meta name="keywords" content={t('meta.keywords')} />
</Helmet>
);
}
import { useTranslation } from 'react-i18next';
function LazyComponent() {
const { t, ready } = useTranslation('common', { useSuspense: false });
if (!ready) {
return <div>Loading...</div>;
}
return <div>{t('welcome')}</div>;
}
// webpack.config.js
module.exports = {
// ...
plugins: [
new i18nextWebpackPlugin({
languages: ['en', 'th', 'ja'], // Only bundle needed languages
defaultLanguage: 'en'
})
]
};
// Simple in-memory cache
const translationCache = new Map();
function getTranslation(language, key) {
const cacheKey = `${language}:${key}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey);
}
const translation = loadTranslation(language, key);
translationCache.set(cacheKey, translation);
return translation;
}
describe('i18n', () => {
const languages = ['en', 'th', 'ja'];
languages.forEach(language => {
i18n.changeLanguage(language);
test('welcome message', () => {
const { t } = renderHook();
expect(t('welcome')).toBeDefined();
});
test('pluralization', () => {
const { t } = renderHook();
expect(t('items', { count: 1 })).toBeDefined();
expect(t('items', { count: 5 })).toBeDefined();
});
});
});
# Take screenshots in different languages
npm run test:visual --locale=en
npm run test:visual --locale=th
npm run test:visual --locale=ja
// Test string expansion
function pseudoLocalize(text) {
return text.split('').join(' ');
}
// "Hello" → "H e l l o "
// Bad: Hardcoded string
<h1>Welcome</h1>
// Good: Translated string
<h1>{t('welcome')}</h1>
// Bad: Concatenation
<p>{t('hello')} {name}!</p>
// Good: Use interpolation
<p>{t('greeting', { name })}</p>
// Bad: No pluralization
<p>You have {count} items</p>
// Good: With pluralization
<p>{t('items', { count })}</p>
// Bad: No RTL support
<div className="container">
// Good: RTL support
<div className="container" dir={isRTL ? 'rtl' : 'ltr'}>
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';
const resources = {
en: {
translation: {
welcome: 'Welcome',
login: 'Login',
logout: 'Logout'
}
},
th: {
translation: {
welcome: 'ยินดีต้อนรับ',
login: 'เข้าสู่ระบบ',
logout: 'ออกจากระบบ'
}
}
};
i18n
.use(HttpApi) // Load translations from API
.use(LanguageDetector) // Detect user language
.use(initReactI18next({
resources,
fallbackLng: 'en',
interpolation: { escapeValue: false },
react: {
useSuspense: false
}
}))
.init();
export default i18n;
// locales/en.json
{
"common": {
"welcome": "Welcome",
"login": "Login",
"logout": "Logout",
"save": "Save",
"cancel": "Cancel"
},
"auth": {
"login": {
"title": "Login to Your Account",
"email": "Email Address",
"password": "Password",
"button": "Login",
"forgot": "Forgot Password?",
"signup": "Don't have an account? Sign up"
},
"signup": {
"title": "Create Your Account",
"name": "Full Name",
"email": "Email Address",
"password": "Password",
"confirmPassword": "Confirm Password",
"button": "Sign Up",
"login": "Already have an account? Login"
}
},
"dashboard": {
"title": "Dashboard",
"overview": "Overview",
"analytics": "Analytics",
"settings": "Settings",
"logout": "Logout"
}
}
// locales/th.json
{
"common": {
"welcome": "ยินดีต้อนรับ",
"login": "เข้าสู่ระบบ",
"logout": "ออกจากระบบ",
"save": "บันทึก",
"cancel": "ยกเลิก"
},
"auth": {
"login": {
"title": "เข้าสู่ระบบของคุณ",
"email": "อีเมล",
"password": "รหัสผ่าน",
"button": "เข้าสู่ระบบ",
"forgot": "ลืมรหัสผ่าน?",
"signup": "ยังไม่มีบัญชี? ลงทะเบียน"
},
"signup": {
"title": "สร้างบัญชีของคุณ",
"name": "ชื่อเต็ม",
"email": "อีเมล",
"password": "รหัสผ่าน",
"confirmPassword": "ยืนยันรหัสผ่าน",
"button": "ลงทะเบียน",
"login": "มีบัญชีอยู่? เข้าสู่ระบบ"
}
},
"dashboard": {
"title": "แดชบอร์ด",
"overview": "ภาพระกับ",
"analytics": "การวิเคราะห์",
"settings": "การตั้งค่า",
"logout": "ออกจากระบบ"
}
}
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const { changeLanguage } = i18n;
const languages = [
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
{ code: 'th-TH', name: 'ไทย', flag: '🇹🇭' },
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
{ code: 'pt-BR', name: 'Português', flag: '🇧🇷' }
];
const handleLanguageChange = (language) => {
changeLanguage(language);
localStorage.setItem('userLanguage', language);
};
return (
<div className="language-switcher">
{languages.map(lang => (
<button
key={lang.code}
onClick={() => handleLanguageChange(lang.code)}
className={i18n.language === lang.code ? 'active' : ''}
>
<span className="flag">{lang.flag}</span>
<span className="name">{lang.name}</span>
</button>
))}
</div>
);
}
---
## Quick Start
### React i18next Setup
```bash
npm install i18next react-i18next i18next-browser-languagedetector
// i18n.js
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: {
translation: {
welcome: 'Welcome',
goodbye: 'Goodbye'
}
},
th: {
translation: {
welcome: 'ยินดีต้อนรับ',
goodbye: 'ลาก่อน'
}
}
},
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
})
import { useTranslation } from 'react-i18next'
function App() {
const { t, i18n } = useTranslation()
return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => i18n.changeLanguage('th')}>
Switch to Thai
</button>
</div>
)
}
// ❌ Bad - Hardcoded
<h1>Welcome</h1>
<p>Hello, user!</p>
// ✅ Good - Translation keys
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: user.name })}</p>
// ❌ Bad - No pluralization
{t('item', { count: items.length })} // Always "item"
// ✅ Good - Pluralization
{t('item', { count: items.length })} // "item" or "items"
/* ❌ Bad - Left-aligned only */
.text {
text-align: left;
}
/* ✅ Good - RTL-aware */
.text {
text-align: start; /* Adapts to direction */
}
25-internationalization/localization/) - Content translation25-internationalization/multi-language/) - Multi-language support25-internationalization/rtl-support/) - Right-to-left languagesxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience