-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
feat: Primitives for i18n routing #11396
feat: Primitives for i18n routing #11396
Conversation
🦋 Changeset detectedLatest commit: c63b0d4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
I'm not sure i understand, in your i18n example, does the |
In the example it would be directly in the root folder. A Any incoming request to |
okay, and how do you determine the current language then ? from what i understand, the language info is never explicitly described in the url. if we suppose a "translations" page on the svelte site listing the different translated versions, what would the |
The language would be part of the URL. That's the difference between a rewrite and a redirect. A rewrite doesn't change the URL, it just changes which route get's resolved. If you have a As for what you write in the For example you could have a
With this hook configured, you could continue writing "/about" as your Both these hooks are just slots where you can add your own free-form functions. All this is just a suggestion for how you could manage the language-state in the URL. You can implement whatever behaviour you like. I've annotated the diagram to hopefully make it clearer what get's written and which values are what: |
That's much clearer, i am not really used to this kind of logic yet, thx a lot for the detailed explanation! |
I think it makes sense to keep it low level. I'm not sure yet if |
I'll be splitting this PR into two PRs, one for each hook. Once the new PRs are submitted I will close this one |
Instead of a preprocessor or a |
Taking another look at this now that #11537 has landed. To be honest I'm quite sceptical about Given that, should we close this PR? |
Thanks @LorisSigrist for your work in this space. I agree with Rich here. From the public interface perspective, having another For the record, here's what I currently have for sveltevietnam.dev... /**
* If going from a localized url to a non-localized url,
* reroute to keep the lang segment. For example:
* navigate from `/en/blog` to `/events` will reroute to `/en/events`
*
* This allows all links in site to exclude lang segment but user
* still sees a consistent display language, unless they change it explicitly
*/
beforeNavigate(({ to, cancel, from }) => {
const fromLang = from?.params?.lang;
const toLang = to?.params?.lang;
if (to && fromLang && !toLang) {
cancel();
const localized = localizeUrl(to.url, fromLang, LANGUAGES);
goto(localized);
}
}); ...coupled with some code in let languageFromUrl = getLangFromUrl(url, LANGUAGES);
const referer = request.headers.get('Referer');
if (referer) {
const urlReferer = new URL(referer);
if (urlReferer.origin === url.origin) {
locals.internalReferer = urlReferer;
}
}
if (!languageFromUrl) {
// if user comes from an internal link with lang, redirect to the same lang
// this is for progressive enhancement when JS is unavailable,
// otherwise the beforeNavigate hook in [[lang=lang]]/+layout.svelte will
// handle the redirection with kit client-side router
if (locals.internalReferer) {
languageFromUrl = getLangFromUrl(locals.internalReferer, LANGUAGES);
if (languageFromUrl) {
return Response.redirect(localizeUrl(url, languageFromUrl, LANGUAGES), 302);
}
}
} Above setup allows me to omit all lang segment in |
I had suspected that the @vnphanquang I have tried that, but unfortunately it doesn't quite cut it. The Thanks for your feedback everyone, I'm very excited about the new |
@LorisSigrist yeah in case JS is not available, that's when the |
Closes #11223
Closes #5703
This PR adds the routing-primitives necessary to implement any i18n routing strategy, including ones with translated slugs. It adds:
The API for interacting with these is open for debate. Currently they are implemented as "router hooks", where you can add function in
hooks.router.js
, but that's just because they have got to go somewhere.resolveDestination({ from , to }): URL
: Allows you to rewrite any outgoing navigation.rewriteURL({ url }): URL
: Allows you to rewrite any incoming request and change which route get's resolvedBoth hooks run on both client and server.
With these primitives, you can now implement i18n routing like so:
Why these primitives?
Before we can implement i18n routing we first need to agree on some requirements. I went with these two:
src/routes
when opting in to i18n routingThe primitives I'm proposing directly follow from these requirements. If you look at the diagram above, you can clearly see them in action.
How do they work
resolveDestination
resolveDestination
get's applied to any outgoing navigation that goes through SvelteKit APIs. That includes:href
,action
andformaction
attributes (implemented with preprocessor)goto
callsredirect
callsIt takes in the current url, and the destination url an returns a new destination url:
rewriteURL
rewriteURL
get's applied before a routeID is resolved. It gives you the opportunity to change which route get's resolved. You could for example rewrite/en/about
to/about
, so that the routeID/about
will be used. It currently get's applied after static assets are resolved, so you can't use it to resolve a static file. That usecase is already covered withhandle
.It is roughly equivalent to NextJS's
afterFiles
rewrite hook.Example i18n routing implementation
I've created a few gists that implement different routing strategies using these primitives:
Addressing some Concerns
There are some valid concerns that I expect people to have. I'm going to preface them here.
It's unclear when
resolveDestination
get's appliedI have shown this to a couple people, and they all quickly picked up on where
resolveDestination
get's applied. It's all the stuff that goes through SvelteKit APIs. That means:href
and other link attributes in markupgoto
sredirects
Any direct network interaction with
fetch
or manually createdResponse
objects remains unaffected.The distinction quickly becomes intuitive.
It's too low level
The primitives admittedly are very low level, but I can't think of any higher level alternative that doesn't also severely limit which strategies can be implemented. If you have a higher level design that's just as flexible, please let me know.
You could mess up what's a data-request and what isn't
Yes, this is one of my concerns with the current implementation aswell. It's possible to misuse the
rewriteURL
hook to turn data-requests into non-data requests, and vice-versa. It's worth considering having a rule that the "data-request" status is determined by the original URL, not the rewritten one.Alternatives
I do believe that these two primitives are absolutely necessary for i18n routing, but the user-facing API could abstract them away. Many other frameworks offer different i18n routing strategies in their config. SvelteKit could do the same and use these primitives under the hood.
However, if that is done using translated slugs would suddenly become much harder, as SvelteKit would need to include it's own message-loading. The API surface would likely grow more than it would with just these primitives. I believe it's best to leave this abstraction to third-party libraries.
I opened this as a draft PR because I expect there to be some discussion & bugs. This probably won't get merged as-is.
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm test
and lint the project withpnpm lint
andpnpm check
Changesets
pnpm changeset
and following the prompts. Changesets that add features should beminor
and those that fix bugs should bepatch
. Please prefix changeset messages withfeat:
,fix:
, orchore:
.