# Tutorial: localized routing with the language

> Step by step, let's build a sample app with Qwik Speak and a localized router using Qwik City features

## Setup

See [Quick Start](https://robisim74.gitbook.io/qwik-speak/library/quick-start)

## Routing

Let's assume that we want to create a navigation of this type:

* default language (en-US): routes not localized `http://127.0.0.1:4173/`
* other languages (it-IT): localized routes `http://127.0.0.1:4173/it-IT/`

In `routes` root level add `[...lang]` directory to catch all routes:

```
src/routes/
│   
└───[...lang]/
        index.tsx
    layout.tsx
```

Now let's handle it. Update `plugin.ts` in the root of the `src/routes` directory:

*src/routes/plugin.ts*

```typescript
import type { RequestHandler } from '@builder.io/qwik-city';
import { setSpeakContext, validateLocale } from 'qwik-speak';

import { config } from '../speak-config';

/**
 * This middleware function must only contain the logic to set the locale,
 * because it is invoked on every request to the server.
 * Avoid redirecting or throwing errors here, and prefer layouts or pages
 */
export const onRequest: RequestHandler = ({ params, locale }) => {
  let lang: string | undefined = undefined;

  if (params.lang && validateLocale(params.lang)) {
    // Check supported locales
    lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang;
  } else {
    lang = config.defaultLocale.lang;
  }

  // Set Speak context (optional: set the configuration on the server)
  setSpeakContext(config);

  // Set Qwik locale
  locale(lang);
};
```

If you want to handle errors or redirects due to the locale, use layouts or pages. For example you could add in `src/routes/layout.tsx`:

```typescript
export const onRequest: RequestHandler = ({ locale, error, redirect }) => {
  // E.g. 404 error page
  if (!locale()) throw error(404, 'Page not found for requested locale');

  // E.g. Redirect
  // if (!locale()) {
  //   const getPath = localizePath();
  //   throw redirect(302, getPath('/page', 'en-US')); // Let the server know the language to use
  // }
};
```

## Usage

Add `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`:

*src/routes/\[...lang]/index.tsx*

```tsx
import { inlineTranslate, useFormatDate, useFormatNumber } from 'qwik-speak';

export default component$(() => {
  const t = inlineTranslate();

  const fd = useFormatDate();
  const fn = useFormatNumber();

  return (
    <>
      <h1>{t('app.title@@{{name}} demo', { name: 'Qwik Speak' })}</h1>

      <h3>{t('dates@@Dates')}</h3>
      <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p>

      <h3>{t('numbers@@Numbers')}</h3>
      <p>{fn(1000000, { style: 'currency' })}</p>
    </>
  );
});

export const head: DocumentHead = () => {
  const t = inlineTranslate();

  return {
    title: t('app.head.home.title@@{{name}}', { name: 'Qwik Speak' }),
    meta: [{ name: 'description', content: t('app.head.home.description@@Localized routing') }],
  };
};
```

Add a `page/index.tsx` to try the router:

*src/routes/\[...lang]/page/index.tsx*

```tsx
import { inlineTranslate } from 'qwik-speak';

export default component$(() => {
  const t = inlineTranslate();

  const key = 'dynamic';

  return (
    <>
      <h1>{t('app.title', { name: 'Qwik Speak' })}</h1>

      <p>{t(`runtime.${key}`)}</p>
    </>
  );
});
```

> Note that it is not necessary to provide the default value in the key once again: it is sufficient and not mandatory to provide it once in the app

> Note the use of a dynamic key (which will therefore only be available at runtime), which we assign to the `runtime` scope

## Change locale

Now we want to change locale. Let's create a `ChangeLocale` component:

*src/components/change-locale/change-locale.tsx*

```tsx
import { useLocation } from '@builder.io/qwik-city';
import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate, localizePath } from 'qwik-speak';

export const ChangeLocale = component$(() => {
  const t = inlineTranslate();

  const pathname = useLocation().url.pathname;

  const locale = useSpeakLocale();
  const config = useSpeakConfig();
  const dn = useDisplayName();

  const getPath = localizePath();

  return (
    <>
      <h2>{t('app.changeLocale@@Change locale')}</h2>
      {config.supportedLocales.map(value => (
        <a key={value.lang} class={{ active: value.lang == locale.lang }} href={getPath(pathname, value.lang)}>
          {dn(value.lang, { type: 'language' })}
        </a>
      ))}
    </>
  );
});
```

> We use the `<a>` tag tag because it is mandatory to reload the page when changing the language

Add the `ChangeLocale` component in `header.tsx` along with localized navigation links:

```tsx
import { Link, useLocation } from '@builder.io/qwik-city';
import { inlineTranslate, localizePath } from 'qwik-speak';

import { ChangeLocale } from '../../change-locale/change-locale';

export default component$(() => {
  const t = inlineTranslate();

  const pathname = useLocation().url.pathname;

  const getPath = localizePath();
  const [homePath, pagePath] = getPath(['/', '/page/']);

  return (
    <>
      <header>
        <ul>
          <li>
            <Link href={homePath} class={{ active: pathname === homePath }}>
              {t('app.nav.home@@Home')}
            </Link>
          </li>
          <li>
            <Link href={pagePath} class={{ active: pathname === pagePath }}>
              {t('app.nav.page@@Page')}
            </Link>
          </li>
        </ul>
      </header>

      <ChangeLocale />
    </>
  );
});
```

## Extraction

We can now extract the translations and generate the `assets` as json. In `package.json` add the following command to the scripts:

```json
"qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT --assetsPath=i18n"
```

```shell
npm run qwik-speak-extract
```

The following files are generated:

```
i18n/en-US/app.json
i18n/it-IT/app.json
translations skipped due to dynamic keys: 1
extracted keys: 9
```

`app` asset for each language, initialized with the default values we provided.

*translations skipped due to dynamic keys* is `runtime.${key}`. During configuration, we provided in `runtimeAssets` a `runtime` file, which we can now create and populate with dynamic keys:

*i18n/\[lang]/runtime.json*

```json
{
  "runtime": {
    "dynamic": "I'm a dynamic value"
  }
}
```

See [Qwik Speak Extract](https://robisim74.gitbook.io/qwik-speak/tools/extract) for more details.

## Development

We can translate the `it-IT` files and start the app:

```shell
npm start
```

## Production

Build the production app in preview mode:

```shell
npm run preview
```

and inspect the `qwik-speak-inline.log` file in root folder to see warnings for missing values or dynamic keys.

### Domain-based routing

#### Prefix always

If you want to use different domains in production, update `speak-config.ts` with the domains supported by each locale, and set the `prefix` usage strategy:

```typescript
export const config: SpeakConfig = {
  defaultLocale: { lang: 'en' },
  supportedLocales: [
    { domain: 'example.com', lang: 'en' },
    { domain: 'example.it', lang: 'it' },
    { withDomain: 'example.com', lang: 'de' }
  ],
  domainBasedRouting: {
    prefix: 'always'
  },
};
```

While in dev mode the navigation will only use the prefix, in production it will use the domain and the prefix:

```
https://example.com/
https://example.com/page
https://example.it/it
https://example.it/it/page
https://example.com/de
https://example.com/de/page
```

> In SSG mode, you can only use `always` as prefix strategy

#### Prefix as needed

If in production you don't want the prefix for the default domains, change the prefix strategy to `as-needed`:

```typescript
domainBasedRouting: {
  prefix: 'as-needed'
},
```

It will result in:

```
https://example.com/
https://example.com/page
https://example.it
https://example.it/page
https://example.com/de
https://example.com/de/page
```

Since the `de` language does not have a default domain, but we have associated another domain, it will automatically keep the prefix.

#### Usage

Update `plugin.ts` to get the language from the domain:

```typescript
import type { RequestHandler } from '@builder.io/qwik-city';
import { extractFromDomain, setSpeakContext, validateLocale } from 'qwik-speak';

import { config } from '../speak-config';

export const onRequest: RequestHandler = ({ params, locale, url }) => {
  let lang: string | undefined = undefined;

  if (params.lang && validateLocale(params.lang)) {
    // Check supported locales
    lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang;
   } else {
    // Extract from domain
    lang = extractFromDomain(url, config.supportedLocales) || config.defaultLocale.lang;
  }

  // Set Speak context (optional: set the configuration on the server)
  setSpeakContext(config);

  // Set Qwik locale
  locale(lang);
};
```

and in `ChangeLocale` component pass the URL instead of the pathname to `getPath`:

```tsx
export const ChangeLocale = component$(() => {
  const url = useLocation().url;

  const config = useSpeakConfig();

  const getPath = localizePath();

  return (
    <>
      {config.supportedLocales.map(value => (
        <a key={value.lang} href={getPath(url, value.lang)}>
          {/*  */}
        </a>
      ))}
    </>
  );
});
```
