24.4 i18n Strings: The i18n/ Directory and T Function
Right, let’s talk about making your site speak other languages. You’re not just slapping a Google Translate widget on this thing and calling it a day, are you? I thought not. That’s a one-way ticket to “le chicken fried” territory. We’re going to do it properly, which means we’re diving into the i18n directory and the T function. This is the bedrock. Get this right, and the rest is (mostly) smooth sailing.
The core idea is simple: you never, ever hardcode a string that a user might see directly into your templates or components. Instead, you assign each string a unique key, and you put the actual human-readable text for each language in separate dictionary files. The framework’s job is to look up the key in the dictionary for the user’s current language and serve the correct string. This process is called “internationalization” — i18n for short (because there are 18 letters between the ‘i’ and the ’n’… clever, huh?).
The i18n Directory Structure
This is where you store your translation dictionaries. By default, you’ll plop this in your root directory. Inside, you create .json files named after the language codes they represent. The simplest structure looks like this:
/i18n
en.json # English
es.json # Spanish
fr.json # French
de.json # German
But let’s be real, a large site will have hundreds of strings. Dumping them all into one massive en.json file is a recipe for madness. You’ll be scrolling forever. So, we use a brilliant feature: namespacing. You can create subdirectories to group your strings logically.
/i18n
/en
common.json # Strings used everywhere: "Submit", "Loading..."
blog.json # Blog-specific strings: "Read More", "Posted on"
product.json # Product strings: "Add to Cart", "In Stock"
/es
common.json
blog.json
product.json
...other languages
This keeps your translations organized and manageable, which you will thank yourself for at 2 AM when you’re trying to find the typo in the “Welcome to our annual sale” string.
The T Function in Action
This is the magic wand. In your components, you use the T function to retrieve a string. The function takes a key, which is the path to your string based on the file structure.
Let’s say in /i18n/en/common.json you have:
{
"greeting": "Hello, world!",
"navigation": {
"home": "Home",
"about": "About Us"
}
}
To retrieve the “About Us” text, your key would be navigation.about. The T function traverses the object structure for you.
Here’s how you’d use it in a Svelte component:
<script>
import { T } from '$lib/i18n';
</script>
<nav>
<a href="/">{T('common:navigation.home')}</a>
<a href="/about">{T('common:navigation.about')}</a>
</nav>
<h1>{T('common:greeting')}</h1>
Note the common: prefix. This tells the function to look inside the common.json namespace. The colon is the crucial separator.
Why This Structure is Your Best Friend
You might be wondering why we bother with the namespace (common:) instead of just letting the function search all files. Two reasons:
- Performance: It’s faster to go directly to a specific file than to search through one gargantuan JSON object.
- Clarity and Collision Avoidance: What if you have a key named
titlein both yourblog.jsonandproduct.json? Without namespaces, which one wins? It’s a mess.blog:titleandproduct:titleare perfectly clear and utterly unambiguous.
The Rough Edges and Pitfalls (This is where I earn my fee)
This system is elegant, but it’s not without its quirks.
- Missing Translations: What happens if a translation for a key is missing in the user’s language? The best practice is to fall back to a default language (usually English). A good
Tfunction implementation will do this automatically, but you must test it. Never assume. - Dynamic Variables: You can’t just concatenate strings. You need interpolation. A good i18n system lets you pass variables into the
Tfunction.// In your en.json "welcome_back": "Welcome back, {name}!"This outputs “Welcome back, Alice!”. This is non-negotiable for any serious project.<script> let user = { name: 'Alice' }; </script> <p>{T('common:welcome_back', { values: { name: user.name } })}</p> - Pluralization: This is the big one. English has two plural forms: singular and plural (“1 file” vs. “2 files”). Other languages are… more enthusiastic. Polish, for instance, has different forms for 1, 2-4, 5-21, and more. Your i18n system must handle this. The typical syntax uses a counter.You’d call it with
"file_count": "{count} file | {count} files"T('common:file_count', { count: numberOfFiles }), and the library handles the logic based on the language’s pluralization rules. If your chosen framework’s system doesn’t handle this well, abandon ship immediately. It’s fundamentally broken.
The T function and the i18n directory are the silent workhorses of a global application. Set them up with care, keep them organized, and they’ll scale with you gracefully. Cut corners here, and you’ll be refactoring every user-facing string in six months, and I won’t be there to help you. I’ll be on a beach, drinking something with an umbrella in it.