Skip to content
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 routing RFC #734

Merged
merged 20 commits into from
Dec 4, 2023
Merged

i18n routing RFC #734

merged 20 commits into from
Dec 4, 2023

Conversation

ematipico
Copy link
Member

@ematipico ematipico commented Oct 19, 2023

Summary

i18n routing support baked into Astro.

Links

@ematipico ematipico changed the title i18n routing i18n routing RFC Oct 20, 2023
@ematipico ematipico marked this pull request as ready for review October 20, 2023 10:52
@ematipico
Copy link
Member Author

@SudoCat @jlarmstrongiv @maddsua @delucis

I am looping you in because you gave excellent support and advice, so it would be great if we could shape this RFC together and answer all the questions.

@maddsua
Copy link

maddsua commented Oct 20, 2023

I might've missed it, but do we have a function that just returns language code?

A use case for it would be like follows:

//  frontmatter
import { getLanguage } from 'whatever';
const lang = getLanguage();  // two-character language code
//  template
<p>
  { {
    uk: 'Щось українською',
    en: 'Something in English',
    es: 'Algo en español',
    fr: 'Quelque chose en français'
  }[lang] }
</p>

This is how I currently do localization for pages that can't be shoved into separate md file for each separate language.

This approach works well in both SSR mode, where we pass language code on the fly, and SSG, where this variable gets passed through getStaticPath's props:

//  src/pages/[...lang].astro

import { localesList } from '@/data/locales.json';

const mkIntlPath = (langs: string[] | 'auto', dynamicRouteSlug: string) => {
  const pageLanguages = langs === 'auto' ? localesList.map(item => item.code) : langs;
  return pageLanguages.map(lang => ({
    //  The first element in localesList is considered to be the default language,
    //  so instead of it's code an undefined value is passed to create 'index' file
    params: { [dynamicRouteSlug]: (lang === localesList[0].code ? undefined : lang) },
    props: { lang }
  }));
};

export function getStaticPaths() {
  return mkIntlPath('auto', 'index');
}
const { lang } = Astro.props;
...
/* 

Will render a page for each language in localesList:
  --> index.html
  --> en.html
  --> fr.html
  --> es.html
  and so...

Call mkIntlPath with an array of language codes
to use something different than app-global language list, like so

mkIntlPath(['es', 'fr'], 'index');

*/

Edits: typo fixes cause I can't write

@SudoCat
Copy link

SudoCat commented Oct 20, 2023

This looks like a great start! I think I'll need a lil more time to gather some more detailed thoughts, but at first glance:

Default Locale and Prefixes

  • I see Redirect to default locale if prefix enabled; is mentioned in goals, but there doesn't seem to be a configuration specification designed. From some brief research, the approaches in other frameworks vary. Next always removes the default prefix, and has some nasty workarounds instead - we should plan ahead to avoid this. Nuxt on the other hand has a "strategy" configuration option, with numerous values. Something like this probably gives the best future compatibility, as we can use an enum which can be extended if new edge cases arise in the future.
  • If i18n is enabled, should all routes be localised? for instance, Astro server endpoints may not want to be localised if their content is not localisation specific. Is this a situation that we should consider in the design (by making it possibly to have routes not localised), or should this just be an edge case we document for awareness?
    • if we are to handle this, my suggested solution would be a rollup extension similar to how SSG/SSR/Hybrid works, with an export const localize: boolean;. This could also make it easier for teams migrating to the new i18n routing from a different solution, as they could opt-in or out as appropriate.

Locale Standardisation and edge cases

  • Should we support any valid string as a locale, or should we strictly only support valid UTS locale identifiers or some other such standard?
  • If we are standardising them, should it be possibly to rewrite them in the URLs by some means? It's quite common for websites in to use /uk for their en-gb websites. (I personally think this is an edge case, and if it is required, a developer can handle it with URL rewriting on their http server/hosting provider/CDN)

Domain Routing

  • If a domain for a locale is defined in the configuration, will Astro still create /[locale]/ routes for that locale on the root domain?
  • Likewise, will the subdomain support the other locales?

I suppose a lot of this is dependent on the adapters?

given:

site: 'https://example.com'
i18n: {
  defaultLocale: 'en',
  locales: ['en', 'fr']
  domains: {
    fr: 'https://fr.example.com'
  }
}

What will the user see if they visit https://example.com/fr?
What will the user see if they visit https://fr.example.com/en?

Middleware

I'm unsure about the middleware setup

  • Is Astro now capable of injecting middleware into a users middleware choices? (I was not aware of this capability)
  • Would this middleware be responsible for determining which locale the user is on? Surely this would need to be executed before the user's middleware, so that their middleware could also read the locale

Usage

I believe we'll need to specify how users can access the Locale, and what information should be available. It will need to be available to pages, endpoints and middleware.

With this in mind, I think this should be added as a separate key on the Astro global, likely named i18n for consistency.

Something like this:

type i18nGlobal = {
  locale: string; // the locale identifier, as defined
  locales: string[] // the list of all available locales
}

I don't believe anything else should be needed on this object immediately. This should provide what is needed to render localised content, and show information relating to other locales if required.

Route Localisation

I don't think the RFC touches on how we'd tackle route localisation.

For individual pages using dynamic routes, this is quite simple - different route, different content. GetStaticPaths in SSG and some data-fetching in SSR.

However for static routes and folders, this gets a little more tricky. As far as I can see, there's only a few options:

  • define in the configuration object:
  • define in filesystem: use another export keyword like prerender in the static pages, and a specifically named file for localising folders themselves - I believe this is similar to how some of the existing Astro localisation plugins work)

The only downside to these solutions is they don't allow for dynamic localisation, driven by an external source like a CMS. I personally do not see this as a problem, as these are unlikely to change frequently, and adding an asynchronous step into routing would be disastrous for performance!

Misc

  • I'm somewhat confused reading the file system based section. It says that the user will not be required to have locale-folders, and then goes on to say these folders should at least be in the pages folder. I think this section needs rewording.

@ematipico ematipico mentioned this pull request Oct 23, 2023
@lilnasy
Copy link
Contributor

lilnasy commented Oct 23, 2023

I don't understand the motivation behind getRelativeLocaleUrl and related functions. What use case do they enable? What would an example application of it look like?

@SudoCat
Copy link

SudoCat commented Oct 23, 2023

It's quite common to only use relative URLs, especially on the frontend - you don't tend to define your navigation using absolute URLs with domains. Most websites I've worked on use absolute pathnames for their navigation links, without defining a hostname.

However you raise a good point - relativeLocaleUrl might be the wrong term - the URL returned is still an absolute pathname, but without a protocol and domain. Perhaps we should rename these to getLocalePathname and getLocaleHref to closer conform to the URL object behaviour.

EDIT: wait no I spoke too soon and had a dumb brain moment. Confusing a Relative Path with a Relative URL. The original names of the functions are correct. A relative URL is a URL without a domain and protocol. An absolute URL is a URL with domain, protocol and pathname. I confused it with a relative/absolute path.

@lilnasy
Copy link
Contributor

lilnasy commented Oct 23, 2023

Thanks, I think that explains it well - you would use these functions to manually construct links.

---
import { getRelativeLocaleUrl } from "astro:18";
import { removeLocaleBase } from "../helper-functions.ts"
---
<a href={getRelativeLocaleUrl('es') + '/' + removeLocaleBase(Astro.request.url)}>Read this article in Spanish</a>
<a href={getRelativeLocaleUrl('it') + '/' + removeLocaleBase(Astro.request.url)}>Read this article in Italian</a>
<a href={getRelativeLocaleUrl('fr') + '/' + removeLocaleBase(Astro.request.url)}>Read this article in French</a>

@jlarmstrongiv
Copy link

jlarmstrongiv commented Oct 23, 2023

@lilnasy I created several helpers in astro-i18n-aut. I found that several settings affect building urls:

  • build output file or directory
  • trailing slash always or never
  • base url
  • defaultLocale vs other locales
  • is a valid and configured locale or not
  • is a locale prefix or a word with the same prefix
  • is a root url or not
  • url concatenation (avoiding /es//path/joined//together)

There’s so many options and config variations that I decided to build these helpers into the package.

The only case I haven’t had the time yet to implement is the relative urls. But, my goal was to avoid manual concatenation to avoid mistakes in joining paths.

I think it would be nice for Astro to provide helpers like the proposal described because there are so many cases and it’s very easy to get it wrong. It’d be nice to use translated urls without thinking about it every time.


I’m traveling this week and haven’t had a chance to go over the full proposal or questions above. I’ll check back this weekend.

@SudoCat
Copy link

SudoCat commented Oct 23, 2023

hmmm your code examples raises a really good point that perhaps we should have a method that can return a specific link in a specific locale, that will automatically handle replacing the locale if found with the one requested, with handling for default locale prefix removal and everything. Something like getRelativeLocaleUrl(locale: string, pathname: string) and getAbsoluteLocaleUrl(locale: string, pathname: string).

As a stretch goal, we should maybe consider handling Astro route urls: getRelativeLocaleUrl(locale: string, pathname: string, params: Record<string, string>?). E.g. getRelativeLocaleUrl("en", "products/[sku]", { sku: 'foo' }) 👀

As @jlarmstrongiv points out, these sorts of helper URLs are great for avoiding common errors when building links.

@Tc-001
Copy link

Tc-001 commented Oct 23, 2023

IMO it currently seems way too repetitive to be used all across a project (even more if migrating to it)

Maybe something like prependLocale(url, locale?)
prependLocale("/foo") // /docs/en/foo

A a nice approach could actually be a special i8n <A> tag that handle everything for you if you just give it the href (without the base or language)

@ematipico
Copy link
Member Author

@maddsua

I might've missed it, but do we have a function that just returns language code?

We don't, but we can definitely add it


@SudoCat

I see Redirect to default locale if prefix enabled; is mentioned in goals, but there doesn't seem to be a configuration specification designed.

Yeah, I want to strike a solution in the middle. I just merged a PR where we implement the Next.js behaviour. Although, I would like to understand the needs and shortcomings.

I like the Nuxt approach, but there are too many options IMHO and their documentation isn't the best because it lacks examples in the documentation.

If i18n is enabled, should all routes be localised?

No, they should not. The idea is that the user decides where to have the localized folders/routes. Astro helps in doing proper routing.

if we are to handle this, my suggested solution would be a rollup extension similar to how SSG/SSR/Hybriad works, with an export const localize: boolean;.

How would this help? You might be onto something here 🤔

I suppose a lot of this is dependent on the adapters?

Yes, and it's possible that domain support needs to make various assumptions in order to work.

What will the user see if they visit https://example.com/fr?
What will the user see if they visit https://fr.example.com/en?

It's possible that the adapters should give the ability to do so, Astro will provide the proper information to do so. However, to set expectations right, I believe that https://example.com/fr needs to be available at least in development and production. https://fr.example.com/en should not be available.

Is Astro now capable of injecting middleware into a users middleware choices? (I was not aware of this capability)

This was always a possibility. With the sequence API you can compose middleware functions.

Would this middleware be responsible for determining which locale the user is on? Surely this would need to be executed before the user's middleware, so that their middleware could also read the locale

Can you expand on this? The user can already read the locale from the headers. The RFC explains why the middleware is set after the user middleware, although we can put it before if you think it's best, although we need to have a stronger argument.

@ematipico
Copy link
Member Author

The reason why I created getRelativeLocaleUrl is mostly to help users to get the base URL right. Astro provides many options that make links very difficult to get right:

  • base
  • trailingSlash
  • build.format

These are a lot combinations, as you might see from the tests. Astro already solved the issue internally, so we should take advantage and help users to create URLs with locales.

Although I am not entirely sold on this utility, because this utility has some assumptions that I don't like. Maybe prependLocale(url, locale?) is what we need?

@SudoCat
Copy link

SudoCat commented Oct 24, 2023

@ematipico

I like the Nuxt approach, but there are too many options IMHO and their documentation isn't the best because it lacks examples in the documentation.

Yeah I definitely think we can do a little better than that. I think for starters, we should just add a routingStrategy configuration, with an enum for values. Just stick to two simple values for now, with room to expand if needed. This could potentially be expanded upon for any domain routing configuration requirements.

type RoutingStrategy = {
  PrefixExceptDefault,
  PrefixAlways,
}

If PrefixExceptDefault is selected, (the default option), then the default locale will not be prefixed, and all other locales shall be prefixed.
If PrefixAlways is selected, then all locales, including the default will use a prefix.

If we need later on, we could consider options like PrefixExceptDomain or something.

No, they should not. The idea is that the user decides where to have the localized folders/routes. Astro helps in doing proper routing.

Right okay, I'd like to see some examples of how this should work to better understand it. I'm not sure it's clear right now how this would work. Would this mean a user would create a folder like src/pages/[locale] to put their localised pages/endpoints in?

How would this help? You might be onto something here 🤔

It would allow users to opt specific pages in to localisation, regardless of where the file lived. It would avoid needing to restructure your project around localisation.

It's not without flaws though - potentially more complicated and could increase build time overheads.

This could also be extended to allow more complicated configuration objects - such as localising static routes or even folder names via index.astro files:

export const localization = { en: 'about', fr: 'à-propos' }

This was always a possibility. With the sequence API you can compose middleware functions.

I've used Sequence before, but I wasn't aware Astro core could inject middleware into the user's defined sequence. I presumed the user's would be required to manually include the middleware in their own sequence. I remember that integrations could not inject middleware into a sequence, so didn't expect there to be an internal API for it.

Can you expand on this? The user can already read the locale from the headers. The RFC explains why the middleware is set after the user middleware, although we can put it before if you think it's best, although we need to have a stronger argument.

Would this be via reading the Accept-Language header, or by parsing the hostname manually to detect the locale?

I guess my question hinges on how Astro handles parsing the locale, and providing that information to the developer, and what exactly the middleware will be doing. My understanding is that most of the i18n implementation will need to happen within Astro before the request even reaches the middleware, at the routing layer. What else would the middleware do?

I might be misunderstanding middleware here, but aren't they processed in order of definition, not reverse? So if createI18nMiddleware is the last middleware in the sequence, then the i18n middleware response will not be available to user-defined middleware. If this i18n middleware is responsible for detecting and parsing the current locale, redirecting routes, wouldn't other middleware need to be aware of these changes first?

For example, in the website I recently worked on, we used middleware for creating our CMS API Client instance, so it was available to all pages. This API Client needed to be created with the locale as a parameter.

Sorry for a lot of questions here, but I think I need to better understand the underlying implementation of the middleware/localisation to know where the middleware should sit in the chain.

@ematipico
Copy link
Member Author

@SudoCat don't worry about the questions, that's what the RFC is for.

I really like the proposal strategy, I will update the RFC accordingly.

Right okay, I'd like to see some examples of how this should work to better understand it. I'm not sure it's clear right now how this would work. Would this mean a user would create a folder like src/pages/[locale] to put their localised pages/endpoints in?

Users can have their localised routes wherever they want, e.g. src/pages/en_AU, or src/pages/blog/en_AU. Conversely, I also understand the need for a possible opt-out feature with an export const localize: boolean. I will think about it.

I guess my question hinges on how Astro handles parsing the locale and providing that information to the developer, and what exactly the middleware will be doing

I see your point now, and it makes sense.

So here's how your middleware functions work. Suppose you have three middleware functions: hello, validation and auth, in this order. The journey of Request and Response happens as follows:

		Request				Request
hello --------> validation --------> auth ┐
                                          |
                                          | Rendering, create Response
                                          |
hello <-------- validation <-------- auth ┘
		Response			Response

auth is the last one the get the Request and the first one to get the Response. If we swap auth with our i18n middleware, the reason why I put it there is because I want to give the chance to the users to handle the Response emitted by the i18n middleware, in case it's needed.

However, your argument that we need to calculate the current locale is very important.

@itsmatteomanf
Copy link
Contributor

We don't, but we can definitely add it

This is a must for implementing <select> to allow for language selection. A list of locales and the current locale are a must...

Ideally, it would allow for adding a name to each locale, so that it can be shown and fetched without relying on separate config options, but that is not a big issue to work around.

@ematipico
Copy link
Member Author

The signature of the APIs has been updated here 4718adb (#734)

@itsmatteomanf
Copy link
Contributor

itsmatteomanf commented Oct 26, 2023

@ematipico I have seen the changes. I get what you are going for with the path argument... but I'd add a simple getCurrentLocale() and getAllLocales() or similar, you can do it easily with these, but it's an additional step.

Maybe return an array of objects with code, domain, root URL, etc.? As with the domain support it gets complicated if you just return the ISO code strings.

@ematipico
Copy link
Member Author

I'll add getCurrentLocale() to the RFC when I have more technical information I can share. I am exploring the API and how it will work both in the backend and frontend. I don't feel comfortable adding an API if I can't explain the internals.

About getAllLocales(), can you give me more info about it? Like use cases

@itsmatteomanf
Copy link
Contributor

Understood, thank you. It's the first RFC I'm following, so I don't know exactly how they work here on Astro 😊

Regarding the getAllLocale(), the main use case would be a page (or component, like a <select>) that lists all locales with links. The first can be done currently relatively easily, the second one it means the values of each option would be a path, which is not the worst (but may be problematic with long URLs). The issues arise when I want to get a name of the language from something like i18next, which wants the language code... what do I do then? Do I manually keep a separate list? Do I strip the URL of extra stuff from values with path = ""?

This is of course less of a problem when you start having domains, as you need that in the front end to redirect, but when you have just a path which includes the language code, the JS is a lot simpler if you just keep it to the language code. Especially if you have very long URLs as I do...

i18n: {
defaultLocaLe: 'en',
locales: ['en', 'es', 'pt_BR', 'pt', 'fr'],
detectBrowserLangauge: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshed: Should it be detectLanguage or something else? Not necessarily coming from a browser.

@ematipico
Copy link
Member Author

Due to time and difficulty issues, we removed the domain support from this RFC. We will land domain support in Astro 4.* under a feature flag. 22d192b

@ematipico
Copy link
Member Author

Calling for consensus for this RFC. The period will last for 3 days and if there's no objections, this RFC will be merged.

@ematipico
Copy link
Member Author

In 089253d, I removed routingStrategy in favour of:

{
	routing: {
		strategy: "pathname",
		prefixDefaultLocale: true
	}
}

As discussed and proposed in #734 (comment)

Also, myself and Chris had a chat, we changed the shape slightly to align it with the current one, and take advantage of the zod .transform function.

@itsmatteomanf
Copy link
Contributor

itsmatteomanf commented Nov 29, 2023

Looks good to me, @ematipico.

The only thing I don't see, but may be there just not explained fully, is a way to get all locales, in the more granular configuration option, with their URLs, even if they are repeated.

This is very useful for the link alternate i18n configurations (and possibly even for the sitemap plugin).

@ematipico
Copy link
Member Author

Looks good to me, @ematipico.

The only thing I don't see, but may be there just not explained fully, is a way to get all locales, in the more granular configuration option, with their URLs, even if they are repeated.

This is very useful for the link alternate i18n configurations (and possibly even for the sitemap plugin).

I'll be happy to add such APIs, could you propose something with the expected result? It would help to get the API right easily

@itsmatteomanf
Copy link
Contributor

itsmatteomanf commented Nov 29, 2023

Looks good to me, @ematipico.
The only thing I don't see, but may be there just not explained fully, is a way to get all locales, in the more granular configuration option, with their URLs, even if they are repeated.
This is very useful for the link alternate i18n configurations (and possibly even for the sitemap plugin).

I'll be happy to add such APIs, could you propose something with the expected result? It would help to get the API right easily

Sorry, was on mobile...

Given a config file like this:

// astro.config.mjs
import {defineConfig} from "astro/config"
export default defineConfig({
    i18n: {
        defaultLocale: 'en',
        locales: ['en', 'es', 'fr', {
            path: "portugues",
            codes: ["pt", "pt-BR", "pt-AO"]
        }]
    }
})

Return something like this:

[
    ["en", "/blog"],
    ["es", "/es/blog"],
    ["fr", "/fr/blog"],
    ["pt", "/portugues/blog"],
    ["pt-BR", "/portugues/blog"],
    ["pt-AO", "/portugues/blog"]
]

Two issues I see, thinking about this now.

The current getRelativeLocaleUrlList() and getAbsoluteLocaleUrlList() don't associate a locale with the URL, making it hard to use in a <select> with a value of the locale or assign a label to the URL (how do I know which is first and get the label? I know it's probably the order I defined them in, but it's not stored anywhere, apart from the config file), and that would apply for this API, too.

Maybe an array of objects here, e.g. { locale: "pt-BR", path: "portugues", url: "/portugues/blog" }, is better?

And probably the same or similar in the other cases? 🤔

@ematipico
Copy link
Member Author

I'll have to think about it, and we can definitely land a new API after we merge the RFC, there's nothing to prevent us from doing so.

@zanhk
Copy link

zanhk commented Dec 1, 2023

@ematipico Would be nice to have a feedback about code duplication (#734 (comment)) as from a quick look on the RFC it's not clear to me how, after the implementation, the projects files should be structured, (I probably missed between the comments) is there a sample repo to showcase how the page structure will look like?

Thanks

@ematipico
Copy link
Member Author

ematipico commented Dec 1, 2023

@ematipico Would be nice to have a feedback about code duplication (#734 (comment)) as from a quick look on the RFC it's not clear to me how, after the implementation, the projects files should be structured, (I probably missed between the comments) is there a sample repo to showcase how the page structure will look like?

Thanks

The RFC is meant for routing and fallback, not content (labels, dictionaries, etc.). We wanted to keep the first round of implementation small and provide as many utilises as possible so the community can build around them. Let's remember that nothing prevents us from adding more features. In fact, we already have a few features that we will add after this first RFC is merged,

The fallback system is one of the most significant changes we added because it adds patterns that weren't possible in Astro core before. Once we deem that feature stable enough, we could use the same logic to generate pages to avoid code duplication.

@zanhk
Copy link

zanhk commented Dec 1, 2023

@ematipico Thanks for the clarification

Co-authored-by: Matteo Manfredi <matteo@manfredi.io>
Copy link
Member

@delucis delucis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working through all the feedback on this RFC @ematipico!

Use `example.com/content/` for the default locale. Use `example.com/[lang]/content/` for other locales.
Trying to access to use `example.com/[defaultLocale]/content/` will result into a 404.

- `domain`: SSR only, it enables support for different domains. When a locale is mapped to domain, all the URLs won't have the language prefix.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `domain`: SSR only, it enables support for different domains. When a locale is mapped to domain, all the URLs won't have the language prefix.
- `domain`: SSR only, it enables support for different domains. When a locale is mapped to domain, the pathname in the URL won't have the language prefix.

@ematipico ematipico merged commit 5d0b3b9 into main Dec 4, 2023
@ematipico ematipico deleted the i18n-routing branch December 4, 2023 13:43
Comment on lines +103 to +111
If a user has a `middleware.ts` file when the i18n routing is enabled, Astro will place its i18n middleware right after the one of the user:

```js
pipeline.setMiddlewareFunction(
sequence(createI18nMiddleware(config), userMiddleware.onRequest)
)
```

By placing the middleware **after** the one of the user, Astro allows users to apply their business logic to the emitted `Response` of the i18n middleware.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused about this part:

Astro will place its i18n middleware right after the one of the user

The code showed that the i18n middleware is before the user's instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, the description is outdated. We place the middleware at the very beginning. I will update the description, good catch, thank you

Comment on lines +314 to +315
For example, if `i18n.locales` contains `['pt', 'fr', 'de']`, and the value of the `Accept-Header` value is `en, fr;q=0.2, de;q=0.8, *;q=0.5`, then
`Astro.preferredLocaleList` will be `['de', 'fr']` because `pt` isn't inside the header, and `en` isn't supported by the website. `de` comes first because it has a highest quality value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Astro.preferredLocaleList will be ['de', 'fr'] because pt isn't inside the header

Isn't pt covered by the *;q=0.5? Or perhaps I misunderstood how the parsing works

Copy link
Member Author

@ematipico ematipico Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but it isn't part of the supported locales inside the configuration, so it isn't returned. If we had to parse and return the locales from the header, it wouldn't add much value, because the users can do that themselves. We cross-match the locales from the header with the ones of the configuration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed now, the description isn't correct, I will fix it.

@bluwy
Copy link
Member

bluwy commented Dec 4, 2023

Oops didn't notice this is merged, but anyways my comments aren't urgent, just some questions that I wonder if it's a typo or not.

@eric-burel
Copy link

eric-burel commented Mar 19, 2024

Hey folks,

I am quite late to the party but I am currently discovering Astro, and I got a good knowledge of Next.js behavior and recent changes regarding i18n, so I'd like to share some feedback after spending ~1h playing with Astro i18n setup and reading the guide :

  • the routing strategy is not super clearly documented yet, though the browser redirection section seems to implicitly mention that it's based on the Accept-Language header, and URL. So Astro doesn't seem to handle cookie based language selection with no URL param (which is ok but would deserve to be said explicitly)

  • the routing seems to rely on middlewares to be able to redirect users, yet doesn't seem to imply per-request SSR as user-land middleware do => it seems to have been mentioned above, but it feels like i18n routing is using an internal feature that could useful to other use cases.

  • can we set a generic [locale] parameter rather than explicit folders en, fr etc.?

  • my site is highly dynamic so I only have a big virtual [...path] route that handles all routes of the site. But I couldn't make i18n work with a page "en/[...path]" or  "[locale]/[...path]", I hit 404 as soon as I setup I18n in Astro.config. Perhaps a mistake on my side, I'll try to craft a repro if possible

  • can we access Astro.preferredLocale without setting up i18n routing? This would allow to easily setup a client-side redirection, without having to explicitly read cookies and activate per-request rendering. Edit: actually this params weirds me out. Either you are doing per-request SSR and its a short cut for Accept-Language or explicit URL, or you are doing static rendering and it's equivalent to the current locale? Am I right?

Most of these may just be documentation issue but I am also unsure about the architecture itself. Astro made a quite opinionated choice here, with a solution quite similar how Next.js worked before version 12.

However since that, Next.js has started considering i18n routing as a part of the broader family of personnalization via redirection. User-land middlewares, which are meant to run also for static pages, allow devs to get full control of i18n.

My feeling is that this is the inevitable direction that all frameworks are meant to take at some point. In Astro, to implement i18n routing, you seem to use an internal feature allowing to have middleware that can run for each HTTP request but also preserve the static nature of the page. Making this feature available to end user would allow them to take full control over i18n instead of relying on the framework provided implementation, but also cover similar personalization use cases.

Edit: got answers to my questions, for now using an Edge Middleware is fine (so I can have a tiny redirection server in front of my Astro server) and discussion should keep going there: #795

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet