-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
i18n brainstorming #553
Comments
I've some thoughts about it...
Should be way to use any plural forms of the phrase based on number value. Something like... // locales/en.json
{
you_have: "You have",
message:["message","messages"]
}
// locales/ru.json
{
you_have: "У вас",
message: ["сообщение","сообщения","сообщений"]
} And we should know plural rules for all languages of the world =)
For example, americans write date this way — 02/23, russians — 23.02. There are a lot of things like currency and so on that may be formatted.
I'd prefer to use third-level domain like
Don't do it. I never seen i18n system with supporting it. There are additional problems like checking URLs for unsupported symbols, complexity of the routes and code, broken links and so on. It is my personal opinion.
Maybe we can find better way and replace this... Developer should be able to use default language visually. It will make content creation simpler for the author.
My position that Sapper doesn't need built in i18n system at all. Only a small fraction of all websites needs to be multilanguage. We can make separate Sapper plugin for i18n providing, or we can use one of many existing libs. PS: Sorry for my poor english... |
Some thoughts:
|
Maybe better way is component-scoped localizations? Like in Vue-i18n plugin. |
Thanks for this input, it's very valuable. Just to clarify my thinking a bit: if we have locales in .json files, Sapper can turn those at build time into JavaScript modules. This is what I mean when I say that we're potentially able to do things in Sapper — 'precompiled' translations with treeshaken i18n helpers — that might be a little difficult with more general-purpose tools. So: PluralsThis locale file... // locales/ru.json
{
you_have: "У вас",
message: ["сообщение","сообщения","сообщений"]
} could be compiled into something like this: import { create_plural } from '@sveltejs/i18n/ru';
export default {
you_have: 'У вас',
message: create_plural(['сообщение', 'сообщения', 'сообщений'])
};
(Having said that, the examples on https://www.i18njs.com point towards using more complete sentences as keys, i.e. In an ideal world, someone would have already encoded all the different pluralisation rules already in a way that we can just reuse. I don't know if that's the case. FormattingI wonder if that can be done with symbols like // locales/fr.json
{
"Your payment of %c for %n widgets is due on %d":
"Votre paiement de %c pour %n widgets est dû le %d."
} import { format_currency, format_date } from '@sveltejs/i18n/fr';
export default {
'Your payment of %c for %n widgets is due on %d': (c, n, d) =>
`Votre paiement de ${format_currency(c)} pour ${n} widgets est dû le ${format_date(d)}.`
}; (Am glossing over the pluralisation of 'widgets' and the nuances of date formatting — '1 April' vs '1 April 2019' vs 'tomorrow' or 'is overdue' — but you get the general thrust.) Localised URLs
I had a conversation on Twitter recently providing one data point to the contrary. I think you're right that it's very rare, though I have to wonder if that's because of the sheer difficulty of it with existing tools (i.e. the same reason it can't be done in userland in Sapper). Django supports it — see the example of At the very least, if we did it it would be opt-in — you just need to choose between Localisation in components
Yeah, I think this is probably true, though I wonder if it makes it harder to keep localisations current. Anyway, I did have one idea about usage — maybe there's a clever way to use tagged template literals: <p>{$t`Welcome back ${name}`}</p>
We shouldn't rule it out. Separate .json files would definitely make the implementation easier though...
Ваш английский лучше моего русского 😀
Can you expand on that? Sapper is already using stores so there's no additional weight there — do you mean the component subscriptions? I guess it could be
Interesting... can you elaborate? I take it a
I guess this is where precompilation could come in handy — we could generate // locales/en.json
{
"Hello": "Hello",
"Welcome back, %s": "Welcome back, %s"
} // locales/en-au.json — the existence of this file causes a module to be created
// that uses Australian currency formatter etc
{
"Hello": "G'day mate"
// other keys fall back to en.json
}
Yeah, I can see that. I guess it could be as simple as an option — A few things that didn't occur to me last night: Constant URLsNot every URL should be localised — static assets, for example, but also probably some pages and server routes. Maybe need a way to distinguish between them. RTL languagesNo idea what's involved here. SEOSome good information here. Looking at all this, I can certainly see why someone would say 'this is too much complexity, it should be handled by third party tools'. On the contrary I think that's probably why it should be handled by Sapper — it's a hard problem that is very difficult to deal with entirely in userland, and which is prone to bloaty solutions. |
One thing that has been helpful in some of my implementations is partial localization. If a key does not exist in one language, it will fallback to (in my case) English. |
Rules are described by Mozilla — https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals . But I don't think they are cover all possible languages. So ability to make custom plural function will be very useful for natives from lost pacific islands. Reading whole Mozilla's Localization section may give some other thoughts about i18n,l10n and even l12y.
wow, it's look amazing!
Maybe we can store strings as JSON property names? And autogenerate default language json file when compiling. Then translators can look for differs in other json files. // locales/en.json
{
"Military-grade progressive web apps, powered by Svelte": "Military-grade progressive web apps, powered by Svelte",
"You are in the army now, ${name}": "You are in the army now, ${name}"
}
// locales/ru.json
{
"Military-grade progressive web apps, powered by Svelte": "Прогрессивные веб-приложения военного качества на платформе Svelte",
"You are in the army now, ${name}": "Теперь ты в армии, ${name}"
} |
This might come across as a little pedantic, but localisation and translation aren't quite the same thing, even though the terms are often used interchangeably. Localisation is about adapting to a specific region (locale), while translation is about adapting to a specific language. One locale might need multiple languages, and one language can exist in multiple locales. But I don't think it's correct to have a folder In practice, localisation might involve any kind of change to a website, or no change at all. So I think it has to be handled with feature flags or other configuration. |
Thoughts: A fallback language stack. If a key is missing, it could look in the next language file and finally fallback to the language key itself. For example: How about a directive? It would be pretty awesome if there was a hook that would allow to send new strings to an automatic translation service. If that's too much, perhaps a list of translation keys in the manifest? By the way, I'm using this translation store in a project:
Definitely not ideal. I would prefer if there were a $session or $cookie store that I could derive from. |
Having worked extensively on projects that involve multiple translations (I was involved for many years on building a platform that ingested language files from institutions like Oxford Publishing with internationalized projects and so forth), I can say with certainty this is a massive rabbit hole. That said, I applaud the effort and 100% support it. Re: Localized URLs, I'm firmly on the side of the fence that it should be opt-in. I can see both scenarios wanting to use it and not. A good chunk of the time I won't want URLs saying anything about language, but something where language is explicit to the business or organizational (or individual) aims of a website, sometimes it will be wanted in the URL. #depends |
I really like this direction, whatever form it ends up taking, and as long as it's opt in (it appears that it would be): <p>{$t`Welcome back ${name}`}</p> |
I think i18n should be designed in sveltejs/sapper as a common ground between translation features and developer features and, yes, all of them are opinionated! From the translation oriented features, I think it's worth to take look at https://projectfluent.org/ as well as ICU message format ( http://userguide.icu-project.org/formatparse/messages ). They have put a lot of effort in designing a dsl language to keep translation logic out of application logic. Maybe just swapping json keys in translation files is too simplistic for the inherent complexity of language.
It would be great to see sveltejs/sapper support out of the box one of these formats/libraries, maybe opting-in. I have no clue how to codesplit translations, but it would be great to come up with a convention to lazy load translations as components are loaded into the application. |
I've received lots of really helpful pointers both here and on Twitter — thanks everyone. Having digested as much as I can, I'm starting to form some opinions... dangerous I know. Here's my current thinking: The best candidate for a translation file format that I've seen yet is the 'banana' format used by MediaWiki (it also forms the basis of jquery.i18n). It seems to be able to handle most of the hellacious edge cases people have written about, while avoiding a) boilerplate, b) wheel reinvention and c) being prohibitively difficult for non-technical humans to read. (Oh, and it's JSON.) The fact that it's used by something as large as MediaWiki gives me hope. If anyone has experience with it and is alarmed by this suggestion, please say so! (One thing — I haven't quite figured out where number/date/currency formatting fit in.) No idea why it's called 'banana'. Here's an example: // en.json — input
{
"@metadata": {
"authors": []
},
"hello": "Hello!",
"lucille": "It's {{PLURAL:$1|one banana|$1 bananas|12=a dozen bananas}} $2. How much could it cost, $3?"
} I think it'd be possible to compile that to plain JavaScript, so that you could output a module you could consume like so: import t from './translations/en.js';
console.log(t.hello); // Hello!
console.log(t.lucille(1, 'Michael', '$10')); // It's one banana Michael. How much could it cost, $10? For code-splitting, this is what I'm thinking: Suppose you have global translations in
The second of these files might look like this: import translations from './0.js';
export default Object.assign({}, translations, {
avatar: 'Avatar',
notifications: 'Notifications',
password: 'Password'
}); The route manifest for {
// settings/index.svelte
pattern: /^\/en/settings\/?$/,
parts: [...],
i18n: () => import('./i18n/1.js')
} So when you first load the page, 0.js gets loaded, but when you navigate to The banana format does steer us away from this... <p>{t`Hello ${name}!`}</p> ...and towards this: <p>{t.hello(name)}</p> I'm not convinced that's such a bad thing — it's certainly less 'noisy', and forces you to keep your default language .json file up to date (which, combined with the tooling suggested above, is probably the best way to keep translations up to date as well). @thgh yep, should definitely have some sort of fallback. Not sure what this would look like — the simplest would obviously be to just have a single default. I'm not so sure about a directive, since it would entail changes to Svelte, and would make it harder to use translations in element attributes (or outside the template, in the |
@Rich-Harris I just drop it in here: https://github.com/lingui/js-lingui What I like with this library is the tooling they have: Where you can:
The last part is particularly interesting, rather than you create a bunch of ids for translation, it uses the actual content in translating. That way it make's it easy for any translator to edit it, heck anyone with a text editor can add it and knows what to do. eg. extracting translations from component might look like this (taken from js lingui wiki) {
"Message Inbox": "",
"See all <0>unread messages</0> or <1>mark them</1> as read.": "",
"{messagesCount, plural, one {There's {messagesCount} message in your inbox.} other {There're {messagesCount} messages in your inbox.}}": "",
"Last login on {lastLogin,date}.": "",
} And a translated version would look like this: {
"Message Inbox": "Přijaté zprávy",
"See all <0>unread messages</0> or <1>mark them</1> as read.": "Zobrazit všechny <0>nepřečtené zprávy</0> nebo je <1>označit</1> jako přečtené.",
"{messagesCount, plural, one {There's {messagesCount} message in your inbox.} other {There're {messagesCount} messages in your inbox.}}": "{messagesCount, plural, one {V příchozí poště je {messagesCount} zpráva.} few {V příchozí poště jsou {messagesCount} zprávy. } other {V příchozí poště je {messagesCount} zpráv.}}",
"Last login on {lastLogin,date}.": "Poslední přihlášení {lastLogin,date}",
} It also introduces slots, which is to be honest a big deal in i18n. With translations, you probably want to style a word inside a translation. Old solution would add a new message id for that particular item, even though the whole translation supposed to be treated as one unit. The problem taking out those text inside the translation message is that it looses context. If a translator just see a word without a context, then he/she could probably give a different translation not intended for the actual message. I think it's the cleanest solution I have seen among any library. Shout out to @tricoder42 for creating such an awesome library. |
Hey everyone, thanks @thisguychris for mention. I read the thread briefly and I have few suggestions if you don't mind:
I would really recommend this approach for two reasons:
There's actually: Plural rules for very large number of languages are defined in CLDR. There're lot of packages on NPM which parse CLDR data, like make-plural. Few languages are missing though (e.g. Haitian and Maori).
// locales/fr.json
{
"Your payment of %c for %n widgets is due on %d":
"Votre paiement de %c pour %n widgets est dû le %d."
} import { format_currency, format_date } from '@sveltejs/i18n/fr';
export default {
'Your payment of %c for %n widgets is due on %d': (c, n, d) =>
`Votre paiement de ${format_currency(c)} pour ${n} widgets est dû le ${format_date(d)}.`
}; ICU MessageFormat uses argument formatters:
Formatting of I've been thinking a lot about this approach and it's useful when you want to change date format in different locales:
but you could achieve the same in the code as well:
where
I think both approaches have pros/cons and I haven't decided yet which one to use.
I've just had a discussion via email with one user about this. I'm thinking about webpack plugin, which could generate i18n files for each chunk automatically. I haven't figure out how to load it automatically, but the route manifest that you've posted might solve it as well. Just flushing some ideas I've been playing with in past weeks :) |
@thisguychris suggested exactly I want to see in i18l system! And one more advantage — using Perhaps, autogenerated json structure may have the structure of nested components: {
"App": {
"My app":"Моё приложение",
},
"App.NestedComponent":{
"Ave, ${name}!": "Славься ${name}!"
},
"App.AnotherNestedComponent":{
"Ave, ${name}!": "Да здравствует ${name}!"
}
} It will encapsulate the phrases in its components. Useful for cases when same phrase may have various translations in different contexts. |
I wanted to chime in just to say that all the issues mentioned in the original issue above, I am experiencing this on my latest project. It's a web version of a mobile app, needs to support 19 languages at launch and is completely api driven. I was delighted to hear that this is being considered in sapper! |
Thanks @thisguychris, @tricoder42 — Lingui is incredibly impressive. The thought and care that has gone into the tooling is amazing. I've been thinking more about strings versus keys, and I'm coming down on the side of keys, for a couple of different reasons. (For clarity, I'm not suggesting Firstly, string-based approaches typically end up with the source language text included in the production build ( Secondly, and perhaps more importantly, I worry about requiring developers to be copywriters. Imagine you have a situation like this... <p>{t`You have no e-mails`}</p> ...and someone points out that we don't hyphenate 'emails' any more — all of a sudden the keys for your other translations are out of date, so you have to go and fix them. Then a copywriter comes along and says that it's the wrong tone of voice for our brand, and should be this instead: <p>{t`Hooray! Inbox zero, baby!`}</p> Of course, that text should also eventually be translated for other languages, but by putting that text in the source code don't we decrease the stability of the overall system? SlotsThe slots feature is very cool. Unfortunately it doesn't really translate (pun not intended) to Svelte, since you can't pass elements and component instances around as values. The closest equivalent I can think of to this... <p>
<Trans>
See all <Link to="/unread">unread messages</Link>{" or "}
<a onClick={markAsRead}>mark them</a> as read.
</Trans>
</p> ...is this: <p>
{#each t.handle_messages as part}
{#if part.i === 0}<a href="/unread">part.text</a>
{:elseif part.i === 1}<button on:click={mark_as_read}>{part.text}</button>
{:else}{part.text}{/if}
{/each}
</p> That assumes that [
{ text: 'See all ' },
{ text: 'unread messages', i: 0 },
{ text: ' or ' },
{ text: 'mark them', i: 1 },
{ text: ' as read.' }
] Obviously that's much less elegant and harder to work with, but maybe that's a rare enough case that it's ok not to optimise for? We can pay for the loss of elegance in other places. Currency and date formattingI hadn't actually realised until today that Distributed components@AlexxNB that's a very interesting case that I hadn't considered. I think it changes the nature of the problem though — since <p>{@t hello_world}</p> But that opens a can of worms, since Svelte now has to have opinions about i18n, which inevitably leads to opinions about project folder structure etc. I think it's probably more practical if components simply expose an interface for passing in translations: <script>
import VolumeSlider from '@some-ui-kit/svelte-volume-slider';
import { t } from '@sapper/app'; // or whatever
let volume = 0.5;
const translations = {
mute: t.mute
};
</script>
<VolumeSlider bind:volume {translations}/> I think we want to avoid referencing component filenames in translation files, since it's not uncommon to move components around a codebase. |
Image another case: when developer changed the text value of any key in the en.json(main language of the app - the source of truth for all translators). Translators even can't to know about this fact. They haven't any built-in tool for actualizing their translations, except looking for diffs on github. sapper --i18n-check ru.json And as result you can get that some phrases was gone, and some new phrases added. |
My two cents on language detection: how about some bootstrap function where the developer can do whatever he wants to detect language and return the result? This way it could analyze URL path, subdomain, cookies, whatever.. less opinionated but still very simple |
Since Sapper/Svelte is a compiler, what about using a single file for all the locales: // locales.json
{
"@metadata": {
"authors": {
"en": ["Lancelot"],
"fr": ["Galahad"],
}
},
"quest": {
"en": "To seek the Holy Grail!",
"fr": "Chercher le Saint Graal !",
},
"favoriteColour": {
"en": "Blue.",
"fr": "Bleu. Non !"
}
} and letting Sapper generate the respective locale files: // en.json
{
"@metadata": {
"authors": ["Lancelot"]
},
"quest": "To seek the Holy Grail!",
"favoriteColour": "Blue."
} // fr.json
{
"@metadata": {
"authors": ["Galahad"]
}
},
"quest": "Chercher le Saint Graal !",
"favoriteColour": "Bleu. Non !"
} This way maintaining keys/values would be much easier in a single file than across several (19?!) files, don't you think? Just my $0.02… |
If the format is compatible with the format output by tools like https://lingohub.com/ (which outputs in a format similar to what @laurentpayot has suggested), that'd be excellent. |
@laurentpayot but how would one add a specific language easily? The format is great, but cumbersome to add/remove languages because it means traversing the single file. This could be solved (altough not ideally) if every sentence/word had a key/number associated. Then it would be easy to see them in that format, but stored in separate files. The "main" language (to with dev is familiar to) would dictate those keys. Any file missing them or having extra ones would be "wrong" |
@khullah Do you mean when several translators are involved and working together? If that's what you mean then I agree it can be cumbersome. |
I really like @laurentpayot's idea. Bear in mind this can also be augmented with tooling — as long as there's a well-understood format and folder structure, you could create an interface for adding and removing languages, as well as translating specific keys (and keeping track of which ones were incomplete, etc). It could even be built in to Sapper's CLI! While I'm here: had a good chat with @thisguychris the other day about authoring, in which he challenged my stance that we should use keys (as opposed to source language strings) for this. He likened it to BEM, having to have a naming structure for stuff that's maintained in a file to which you're tightly coupled at a distance. I think there's merit to that claim. So while I do think that keys have some important advantages...
...it's true that in cases where you get the translations before you build the app, using strings might be preferable. So I guess I still lean towards keys, but my position isn't set in stone. |
Re disambiguation — just reading this piece that was doing the rounds today which contains a great example of a situation where using a source language string as a key will result in suboptimal translations:
|
Localised URLs
I think this is the best option, because:
|
Yes @laurentpayot, that's what i've meant, but not only that. It would be difficult to have some sort of phrasing dictionary from other projects to import from, which would be a great thing. I think removing a language occurs less then adding one. That beeing said, it does help human translators to see and understand context, provide a tasklist, help mantain all langs in sync, etc, as mentioned by @Rich-Harris . And this is actually something I would thrive for - promote whatever is better for the devs (compilation capabilities should be explored at maximum, it is the distinguishing feature from all other frameworks after all). Actually.. just realized that would not be hard to take someLanguage.dictionary.json and pre-fill in that format as well, since keys are kinda like nickames to each phrasing. "Hello" would be filled with a default translation, which translators could later adapt if necessary for the given project. Even more, several files could provide better context + modularization: // greetings or home or xComponent.i18n.json
{
"hello": {
"en": "Hello!",
...
}
// yComponent.i18n.json
{
"message": {
"en": "some message",
},
"variants": {
"en": ["some message","same message!!","Hey, another message"]
},
...
} So yeah, I like your format :) note: l10n of currency, numbers and dates would be in yet another (global) file (if needed, since there is moment.js etc)
Yeap. Way better than the second example you gave. Didn't catch why it isn't simple to do? |
It's just a difference between how React and Svelte work. In React, elements are just variables — you can do this sort of thing... var element = <p>some text</p>;
return <div>{element}</div>; and by extension, you can transform the In Svelte, everything is precompiled. The assumption (which holds for 99% of cases, but not this case) is that the structure of your application can be known at build time. Using that, it can generate code that starts and updates much faster than is possible with a virtual DOM; the lack of support for Lingui-style slots is the tradeoff. |
It seems nobody mentioned URLs were originally intended (if I recall correctly) to serve as a single source for a particular piece of information, independent of presentation. That way, they could either present the information resource in English, Klingon, JSON, binary or whatever, depending on the HTTP negotiation. Nobody does this nowadays, for good practical reasons (which also depend on available technology, which could change), but it was the original intent. And though I may be biased, because the purist in me likes the theoretical elegance, I think the option should be left open for that. Also, the language selection mechanism should be selectable itself. We should be able to configure, or customize, how Sapper determines the language. Localized URLs.I like the idea, but keeping in sync with what I said before, THEORETICALLY, there should be a canonical URL that can present data in any language, also including machine readable ones, and then you can have alternative localized URLs to the same resource, which may suggest a presentational preference for its content. For example...
Anyway, all I'm saying is we should keep that flexibility. EDIT: And then again, it's OK to think of the same information in another language as a separate resource. |
Hello there! I'm a member of the Angular team, and I work on i18n there. I thought that I could share some of my knowledge to help you get started:
|
@Rich-Harris We have more and more libs, each having a lot of problems or even broken by design (race conditions and global state). Most of them are not maintained well. And this is one of the core things most projects outside of North America need. I think we need an official library or even build this into the framework itself. A lot of my friends that I recommend SvelteKit to don't want to migrate from Nuxt precisely because of the lack of proper i18n. Both Nuxt and Next have official support for i18n. |
Although it would be nice, I don't believe that a complete official library is desperately needed at the moment. A well-designed and documented API, enabling to hook into routing system (for managing routes and possibly domains) and the build system (for generating texts and assets), will be more than sufficient for starters. |
Related: I added i18n support to The generated sitemap will include proper multi-lingual site annotations as Google expects. |
There is an entire paragraph at the end of SveleteKit 2 announcement addressing this issue. |
I completely agree. The basic tasks of i18n are easy in SvelteKit once you understand how to use it's various routing techniques to set locale per request (see my instructions on how get going w/o race condition problems - and note that you don't even need a library). As I see it, SvelteKit support would be for the purpose of making advanced tasks simple. Things like:
|
The one advantage I could see to built in i18n support w/ locale management instead of just router hooks is per-language code splitting. I don't think it's possible to code-split your translation strings both by language and per page unless each languages has it's own entry point. |
@stepanorda we (https://inlang.com/), most notably @LorisSigrist who develops Paraglide JS, are pushing PRs (#11396, #11178) towards SvelteKit in collaboration with the Svelte team now. In short, i18n should become better for SvelteKit soon. Higher prioritization from the Svelte team would be welcome ofc. |
Progess 🎉 SvelteKit 2.3.0 includes #11537 from @LorisSigrist |
Further Progress - We released Paraglide JS Adapter SvelteKit which makes use of the new
It's still in prerelease mode, but we would love to get some feedback! |
Something I quickly made for my application is composing keys. |
I don't know if sveltekit has to include i18n directly, but the site and docs should. I've being going around some official international channels in discord and there is not much to see. Mine (spanish) has a fork deployed with docs for svelte but not sveltekit. I think if the main domain had that internationalization integrated more people would collaborate on the translation and expansion of svelte. It seems paraglide could be our savior, but I wonder if the internal team is working on gathering the resources around to make it possible. |
Hi guys. I don't have anything to sell and really just wish to share good experience. For a summary, I used to learn 80x86 assembly, hungarian and typescript (among other things) I'm like you, I hope svelte will keep on kicking a*ses, I just make stuff available, I hope it will fuel your brainstorming and inspire. |
I want to share my idea of using the Svelte compiler to make i18n a lot simpler. I wrote a working plugin to show the idea: My suggestion would be to make a custom $t`${count} count` The compiler will try to find all the translations for the given template string in a global JSON file. // locales.json
{
"${count} count": {
"es": "${count} contar",
"jp": "${count} カウント"
}
} If the compiler finds existing translations we replace the original rune with: $t.get(`${count} count`, {
es: `${count} contar`,
jp: `${count} カウント`,
}) The If the compiler didn't find any existing translations we add the text that needs to be translated to the file. // locales.json
{
"${count} count": {}
} From this point on, the developer can translate all the text inside locales.json with any tool he likes. |
@ivands inlang already has this feature I think via sherlock vscode extension. I think nothing beats inlang when it comes to i18n rn. |
@bugproof, With inlang I personally dislike the fact that you will need to think of property names for every text that needs to be translated. The |
@ivands you need identifiers for messages. here is an in-depth answer why ids are required https://github.com/orgs/opral/discussions/599#discussioncomment-5754261 extracting messages as text seems easier at first until you need to collaborate with translators, designers, product managers, etc. inlang started out with using text as identifier like your solution. see this Reddit post from 3 (!) years ago. @ivands thanks for the compliment re inlang ❤️ |
@samuelstroschein, I think the downsides of using the English source text as ID don't apply to my example. Also in my example, I use AI to translate the text, this would negate the problem altogether. |
@ivands you can't rely solely on AI for translation. At least not yet. Having English as a source is unmaintainable on large projects and teams. I can give you a very simple example: Imagine you missed a coma in the English version. Would you re-translate the whole thing for all the languages? Or manually edit your JSON? And all 100 mentions of it all over your code? If you think that AI is good enough, maybe that's Ok, but for most, it's not. So somebody already translated this string, and it was fine, now he has to go and translate (or at least check the output of the AI) again for all languages? |
Agree 100%. On certain projects, we use an i18n tool that uses text as a source, and it's such a pain when the source text needs to be edited or changed. Thankfully, it doesn't happen often, but when it does, it's pain. |
With Sherlock you don't have to. It generates random identifiers for you. opral/monorepo#1892 |
https://inlang.com/m/gerre34r/library-inlang-paraglideJs/usage#complex-formatting
Most projects I worked on require plurals - and many languages handle these differently so it's not just a simple situation of a switch/case in JS. |
@gabrielstellini 100%. We talked to a bunch of translators before settling on fluent for all of our translation files. Their js lib doesn't support tree-shaking, so we build https://github.com/willfarrell/fluent-transpiler to meet our needs. |
@gabrielstellini Paraglide JS has variant support, which includes pluralization, gendering, and more use cases on the dev branch. The release of Paraglide JS 2.0 is blocked until we have lix 1.0. You can read more here. In addition, Paraglide JS 2.0 can load any translation file. Doesn't matter if it's ICU, Fluent, arb, XML, or something else (@willfarrell). When is the release of Paraglide JS 2.0? Probably Q1 2025 if we manage to get lix 1.0 stable. |
We've somewhat glossed over the problem of internationalisation up till now. Frankly this is something SvelteKit isn't currently very good at. I'm starting to think about how to internationalise/localise https://svelte.dev, to see which parts can be solved in userland and which can't.
(For anyone unfamiliar: 'Internationalisation' or i18n refers to the process of making an app language agnostic; 'localisation' or l10n refers to the process of creating individual translations.)
This isn't an area I have a lot of experience in, so if anyone wants to chime in — particularly non-native English speakers and people who have dealt with these problems! — please do.
Where we're currently at: the best we can really do is put everything inside
src/routes/[lang]
and use thelang
param inpreload
to load localisations (an exercise left to the reader, albeit a fairly straightforward one). This works, but leaves a few problems unsolved.I think we can do a lot better. I'm prepared to suggest that SvelteKit should be a little opinionated here rather than abdicating responsibility to things like
i18next
, since we can make guarantees that a general-purpose framework can't, and can potentially do interesting compile-time things that are out of reach for other projects. But I'm under no illusions about how complex i18n can be (I recently discovered that a file modified two days ago will be labeled 'avant-hier' on MacOS if your language is set to French; most languages don't even have a comparable phrase. How on earth do you do that sort of thing programmatically?!) which is why I'm anxious for community input.Language detection/URL structure
Some websites make the current language explicit in the pathname, e.g. https://example.com/es/foo or https://example.com/zh/foo. Sometimes the default is explicit (https://example.com/en/foo), sometimes it's implicit (https://example.com/foo). Others (e.g. Wikipedia) use a subdomain, like https://cy.example.com. Still others (Amazon) don't make the language visible, but store it in a cookie.
Having the language expressed in the URL seems like the best way to make the user's preference unambiguous. I prefer
/en/foo
to/foo
since it's explicit, easier to implement, and doesn't make other languages second-class citizens. If you're using subdomains then you're probably running separate instances of an app, which means it's not SvelteKit's problem.There still needs to be a way to detect language if someone lands on
/
. I believe the most reliable way to detect a user's language preference on the server is theAccept-Language
header (please correct me if nec). Maybe this could automatically redirect to a supported localisation (see next section).Supported localisations
It's useful for SvelteKit to know at build time which localisations are supported. This could perhaps be achieved by having a
locales
folder (configurable, obviously) in the project root:Single-language apps could simply omit this folder, and behave as they currently do.
lang attribute
The
<html>
element should ideally have alang
attribute. If SvelteKit has i18n built in, we could achieve this the same way we inject other variables intosrc/template.html
:Localised URLs
If we have localisations available at build time, we can localise URLs themselves. For example, you could have
/en/meet-the-team
and/de/triff-das-team
without having to use a[parameter]
in the route filename. One way we could do this is by encasing localisation keys in curlies:In theory, we could generate a different route manifest for each supported language, so that English-speaking users would get a manifest with this...
...while German-speaking users download this instead:
Localisation in components
I think the best way to make the translations themselves available inside components is to use a store:
Then, if you've got files like these...
...SvelteKit can load them as necessary and coordinate everything. There's probably a commonly-used format for things like this as well — something like
"Willkommen zurück, $1"
:(In development, we could potentially do all sorts of fun stuff like making
$t
be a proxy that warns us if a particular translation is missing, or tracks which translations are unused.)Route-scoped localisations
We probably wouldn't want to put all the localisations in
locales/xx.json
— just the stuff that's needed globally. Perhaps we could have something like this:Again, we're in the fortunate position that SvelteKit can easily coordinate all the loading for us, including any necessary build-time preparation. Here, any keys in
src/routes/settings/_locales/en.json
would take precedence over the global keys inlocales/en.json
.Translating content
It's probably best if SvelteKit doesn't have too many opinions about how content (like blog posts) should be translated, since this is an area where you're far more likely to need to e.g. talk to a database, or otherwise do something that doesn't fit neatly into the structure we've outlined. Here again, there's an advantage to having the current language preference expressed in the URL, since userland middleware can easily extract that from
req.path
and use that to fetch appropriate content. (I guess we could also set areq.lang
property or something if we wanted?)Base URLs
Sapper (ab)used the
<base>
element to make it easy to mount apps on a path other than/
.<base>
could also include the language prefix so that we don't need to worry about it when creating links:Base URLs haven't been entirely pain-free though, so this might warrant further thought.
Having gone through this thought process I'm more convinced than ever that SvelteKit should have i18n built in. We can make it so much easier to do i18n than is currently possible with libraries, with zero boilerplate. But this could just be arrogance and naivety from someone who hasn't really done this stuff before, so please do help fill in the missing pieces.
The text was updated successfully, but these errors were encountered: