Skip to content

Commit

Permalink
Merge pull request #11 from wmertens/lang-route
Browse files Browse the repository at this point in the history
Lang route
  • Loading branch information
mhevery authored Jul 20, 2023
2 parents 487f8e8 + 266ef7c commit 45814ff
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 58 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ src/locale/message.sp.json

### Inline strings

The strings need to be inlined into the application. This is done by running the following command:
The strings need to be inlined into the application. This is done automatically as part of the build.client process.

```bash
npm run i18n-translate
npm run build.client
```

The result of this command is that the browser chunks are generated once for each locale. For example:
Expand All @@ -88,13 +88,13 @@ dist/builld/sp/q-*.js
npm run dev
```

Navigate to `http://localhost:5173`. The resulting language should match your browser language. You can also override the language by adding `?lang=sk` to the URL.
Navigate to `http://localhost:5173`. The resulting language should match your browser language. It will pick `sk` if it can't detect a language, this can happen when you run under StackBlitz for example. You can also override the language by adding `?locale=fr` to the URL.

## Building the application

Here are the steps to build the application for production.

```
```sh
npm run build.client && npm run build.server && npm run i18n-translate && npm run serve
```

Expand Down
7 changes: 2 additions & 5 deletions src/components/header/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ header ul {
header li {
display: inline-block;
margin: 0;
padding: 0;
padding: 15px 10px;
}

header li a,
header li div {
display: inline-block;
padding: 15px 10px;
header li a {
text-decoration: none;
}

Expand Down
49 changes: 19 additions & 30 deletions src/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { component$, getLocale, useStylesScoped$ } from "@builder.io/qwik";
import { component$, useStylesScoped$ } from "@builder.io/qwik";
import { QwikLogo } from "../icons/qwik";
import styles from "./header.css?inline";
import { Link, RouteLocation, useLocation } from "@builder.io/qwik-city";

const LocaleLink = ({
locale,
currentLocale,
location,
}: {
locale: string;
currentLocale: string;
location: RouteLocation;
}) => (
<li>
{locale === currentLocale ? (
{locale === location.params.locale ? (
<div>{locale}</div>
) : (
<a href={`?locale=${locale}`}>{locale}</a>
<a
href={`/${locale}${location.url.pathname.slice(3)}${
location.url.search
}`}
>
{locale}
</a>
)}
</li>
);

export default component$(() => {
const locale = getLocale();
const location = useLocation();
useStylesScoped$(styles);

return (
Expand All @@ -30,33 +37,15 @@ export default component$(() => {
</a>
</div>
<ul>
<LocaleLink locale="en" currentLocale={locale} />
<LocaleLink locale="fr" currentLocale={locale} />
<LocaleLink locale="sk" currentLocale={locale} />
<LocaleLink locale="sp" currentLocale={locale} />
<LocaleLink locale="en" location={location} />
<LocaleLink locale="fr" location={location} />
<LocaleLink locale="sk" location={location} />
<LocaleLink locale="sp" location={location} />
<li>
<a
href="https://qwik.builder.io/docs/components/overview/"
target="_blank"
>
Docs
</a>
<span>|</span>
</li>
<li>
<a
href="https://qwik.builder.io/examples/introduction/hello-world/"
target="_blank"
>
Examples
</a>
</li>
<li>
<a
href="https://qwik.builder.io/tutorial/welcome/overview/"
target="_blank"
>
Tutorials
</a>
<Link href={$localize`/__/blog`}>{$localize`Blog`}</Link>
</li>
</ul>
</header>
Expand Down
28 changes: 15 additions & 13 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ if (!$localizeFn.TRANSLATION_BY_LOCALE) {
});
}

const validateLocale = (locale?: string | null) => {
if (!locale) return;
const validateLocale = (locale: string) => {
if ($localizeFn.TRANSLATION_BY_LOCALE.has(locale)) return locale;
const match = /^([^-;]+)[-;]/.exec(locale);
return match && $localizeFn.TRANSLATION_BY_LOCALE.has(match[1]) && match[1];
return (
(match && $localizeFn.TRANSLATION_BY_LOCALE.has(match[1]) && match[1]) ||
undefined
);
};

/**
Expand All @@ -71,17 +73,17 @@ const validateLocale = (locale?: string | null) => {
*
* @returns The locale to use, which will be stored in the render context.
*/
export function extractLang(
acceptLanguage: string | undefined | null,
url: string,
): string {
let locale;

if (url) locale = validateLocale(new URL(url).searchParams.get("locale"));
if (locale) return locale;

export function extractLang(request: Request, url: URL): string {
// This is not really needed because we handle /locale, but it's here as an example
let locale = url.searchParams.get("locale") || undefined;
if (locale) {
// note that we mutate the URL here, this will update the search property
url.searchParams.delete("locale");
locale = validateLocale(locale);
if (locale) return locale;
}
// Parse the browser accept-language header
const locales = acceptLanguage?.split(",");
const locales = request.headers.get("accept-language")?.split(",");
if (locales)
for (const entry of locales) {
locale = validateLocale(entry);
Expand Down
7 changes: 6 additions & 1 deletion src/locale/message.en.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"locale": "en",
"translations": {
"4608414764123111425": "count: {$PH}",
"7751010942038334793": "Blog",
"7683568525587144503": "/en/blog",
"8399228546444251220": "Counter Example",
"6030848919533485936": "Hello and welcome to the blog",
"2023484548631819319": "Hello world",
"3957345415493603866": "/en/blog/{$PH}/",
"4608414764123111425": "count: {$PH}",
"2954233255021387859": "increment"
}
}
7 changes: 6 additions & 1 deletion src/locale/message.fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"translations": {
"4608414764123111425": "compter: {$PH}",
"8399228546444251220": "Exemple de compteur",
"2954233255021387859": "incrément"
"2954233255021387859": "incrément",
"7683568525587144503": "/fr/blog",
"3957345415493603866": "/fr/blog/{$PH}/",
"7751010942038334793": "Blogue",
"6030848919533485936": "Bonjour et bienvue sur mon blogue!",
"2023484548631819319": "Hello world"
}
}
7 changes: 6 additions & 1 deletion src/locale/message.sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"translations": {
"8399228546444251220": "Počítadlovy Príklad",
"4608414764123111425": "počítadlo: {$PH}",
"2954233255021387859": "pridať"
"2954233255021387859": "pridať",
"7751010942038334793": "Blogu",
"7683568525587144503": "/sk/blog",
"6030848919533485936": "Ahoj a vitajte na blogu",
"2023484548631819319": "Ahoj svet!",
"3957345415493603866": "/sk/blog/{$PH}/"
}
}
7 changes: 6 additions & 1 deletion src/locale/message.sp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"translations": {
"4608414764123111425": "contar: {$PH}",
"8399228546444251220": "Ejemplo de contador",
"2954233255021387859": "incremento"
"2954233255021387859": "incremento",
"7751010942038334793": "Blog",
"7683568525587144503": "/es/blog",
"6030848919533485936": "Hola y bienvenido al blog",
"2023484548631819319": "Hola mundo",
"3957345415493603866": "/es/blog/{$PH}/"
}
}
7 changes: 7 additions & 0 deletions src/routes/[...locale]/blog/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { component$ } from "@builder.io/qwik";
import { useLocation } from "@builder.io/qwik-city";

export default component$(() => {
const location = useLocation();
return <div>Pretend this is the blog text for "{location.params.id}".</div>;
});
11 changes: 11 additions & 0 deletions src/routes/[...locale]/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { component$ } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";

export default component$(() => (
<div>
<p>{$localize`Blog welcome text`}</p>
<Link
href={$localize`/__/blog/${"hello-world"}/`}
>{$localize`Hello world`}</Link>
</div>
));
File renamed without changes.
31 changes: 29 additions & 2 deletions src/routes/layout.tsx → src/routes/[...locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
import { component$, Slot } from "@builder.io/qwik";
import Header from "~/components/header/header";
import type { RequestHandler } from "@builder.io/qwik-city";
import { extractLang } from "../i18n";
import { extractLang } from "~/i18n";

const locales = new Set(["en", "fr", "sk", "sp"]);

export const onGet: RequestHandler = async ({
request,
url,
redirect,
pathname,
params,
locale,
cacheControl,
}) => {
locale(extractLang(request.headers.get("accept-language"), request.url));
if (locales.has(params.locale)) {
// Set the locale for this request
// TODO be case-insensitive
locale(params.locale);
} else {
// Redirect to the correct locale
const guessedLocale = extractLang(request, url);
let path;
if (
params.locale === "__" ||
/^[a-z][a-z](-[a-z][a-z])?/i.test(params.locale)
) {
// invalid locale
// TODO a better way to replace the locale parameter that supports a base path
path = "/" + pathname.split("/").slice(2).join("/");
} else {
// no locale
path = pathname;
}
throw redirect(301, `/${guessedLocale}${path}${url.search}`);
}

// Control caching for this request for best performance and to reduce hosting costs:
// https://qwik.builder.io/docs/caching/
cacheControl({
Expand Down
8 changes: 8 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RequestHandler } from "@builder.io/qwik-city";
import { extractLang } from "~/i18n";

export const onGet: RequestHandler = async ({ request, redirect, url }) => {
const guessedLocale = extractLang(request, url);
console.log(` ➜ GET / - Redirecting to /${guessedLocale}...`);
throw redirect(301, `/${guessedLocale}/${url.search}`);
};

0 comments on commit 45814ff

Please sign in to comment.