Skip to content

Commit

Permalink
Merge pull request #2907 from opral/sveltekit-async-localstorage
Browse files Browse the repository at this point in the history
SvelteKit make `languageTag()` available in server-load functions
  • Loading branch information
LorisSigrist authored Jun 10, 2024
2 parents b6d9f2c + 7fe0bda commit 1356a25
Show file tree
Hide file tree
Showing 16 changed files with 303 additions and 87 deletions.
9 changes: 9 additions & 0 deletions .changeset/late-birds-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@inlang/paraglide-sveltekit": minor
---

Make `languageTag()` and message functions available in server-side load function.

This eliminates the need for
- `event.locals.paraglide.lang` anywhere.
- Manually passing the language tag to message functions that are used in load functions / actions.
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,19 @@ Use this to create a language switcher.
</a>
{/each}
```

## Re-Loading Language-Dependent data

If you have a `load` function that returns data that depends on the language you can tell it to re-run on language changes by calling `depends("paraglide:lang")`.

```ts
// src/routes/+page.server.js
export async function load({ depends }) {
// Paraglide-SvelteKit automatically calls `invalidate("paraglide:lang")` whenever the langauge changes
// This tells SvelteKit to re-run this function whenever that happens
depends("paraglide:lang")
return await myLanguageSpecificData();
}
```

Paraglide-SvelteKit automatically calls `invalidate("paraglide:lang")` when the language changes.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { languageTag } from "$lib/paraglide/runtime"

export const prerender = true
export const trailingSlash = "always"

/**
* @type { import("./$types").LayoutServerLoad}
*/
export function load({ locals, depends }) {
export function load({ depends }) {
// This tells SvelteKit to re-run this load function when the language changes
depends("paraglide:lang")

return {
serverLang: `The language on the server is ${locals.paraglide.lang}`,
serverLang: `The language on the server is ${languageTag()}`,
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { languageTag } from "$lib/paraglide/runtime"
import type { Actions } from "./$types"

export const prerender = false

export const actions: Actions = {
create: async ({ locals }) => {
console.info("create", locals.paraglide.lang)
create: async () => {
console.info("create", languageTag())
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const config = {
prerender: {
//Needed for correctly prerendering <link rel="alternate" hreflang="x" href="y">
origin: "https://example.com",
entries: ["*", "/de"],
},
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
"/": "./inlang/source-code/paraglide/paraglide-sveltekit/docs/why-paraglide.md",
"/getting-started": "./inlang/source-code/paraglide/paraglide-sveltekit/README.md",
"/localised-routing": "./inlang/source-code/paraglide/paraglide-sveltekit/docs/localised-routing.md",
"/advanced-usage": "./inlang/source-code/paraglide/paraglide-sveltekit/docs/advanced-usage.md",
"/serverside-usage": "./inlang/source-code/paraglide/paraglide-sveltekit/docs/serverside-usage.md"
"/advanced-usage": "./inlang/source-code/paraglide/paraglide-sveltekit/docs/advanced-usage.md"
},

"Appendix": {
Expand Down
6 changes: 3 additions & 3 deletions inlang/source-code/paraglide/paraglide-sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@
},
"exports": {
".": {
"import": "./dist/runtime/index.js",
"svelte": "./dist/runtime/index.js",
"types": "./dist/runtime/index.d.ts"
"types": "./dist/runtime/index.server.d.ts",
"browser": "./dist/runtime/index.client.js",
"default": "./dist/runtime/index.server.js"
},
"./internal": {
"import": "./dist/runtime/internal/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
It also adds `<link rel="alternate">` tags to the head of your page
-->
<script lang="ts" generics="T extends string">
import type { I18n } from "./adapter.js"
import type { I18n } from "./adapter.server.js"
import { page } from "$app/stores"
import { browser, dev } from "$app/environment"
import { normaliseBase } from "./utils/normaliseBase.js"
Expand Down Expand Up @@ -39,7 +39,7 @@
*/
$: autodetectedLanguage = i18n.getLanguageFromUrl($page.url)
$: lang = languageTag ?? autodetectedLanguage
$: i18n.config.runtime.setLanguageTag(lang)
$: if (browser) i18n.config.runtime.setLanguageTag(lang)
$: if (browser) document.documentElement.lang = lang
$: if (browser) document.documentElement.dir = i18n.config.textDirection[lang] ?? "ltr"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { createReroute } from "./hooks/reroute.js"
import { base } from "$app/paths"
import { page } from "$app/stores"
import { get } from "svelte/store"
import { dev } from "$app/environment"
import { parseRoute, serializeRoute } from "./utils/route.js"
import {
normaliseBase as canonicalNormaliseBase,
type NormalizedBase,
} from "./utils/normaliseBase.js"
import { createExclude } from "./exclude.js"
import { guessTextDirMap } from "./utils/text-dir.js"
import {
prettyPrintPathDefinitionIssues,
resolveUserPathDefinitions,
validatePathTranslations,
type PathDefinitionTranslations,
} from "@inlang/paraglide-js/internal/adapter-utils"
import type { ParamMatcher } from "@sveltejs/kit"
import type { Paraglide } from "./runtime.js"
import { PrefixStrategy } from "./strategy.js"
import type { I18nUserConfig } from "./adapter.server.js"

/**
* The _resolved_ configuration for the i18n instance.
*/
export type I18nConfig<T extends string> = {
runtime: Paraglide<T>
translations: PathDefinitionTranslations<T>
exclude: (path: string) => boolean
defaultLanguageTag: T
matchers: Record<string, ParamMatcher>
prefixDefaultLanguage: "always" | "never"
textDirection: Record<T, "ltr" | "rtl">
seo: {
noAlternateLinks: boolean
}
}

/**
* Creates an i18n instance that manages your internationalization.
*
* @param runtime The Paraglide runtime.
* @param options The options for the i18n instance.
* @returns An i18n instance.
*
* @example
* ```ts
* // src/lib/i18n.js
* import * as runtime from "../paraglide/runtime.js"
* import { createI18n } from "@inlang/paraglide-sveltekit"
*
* export const i18n = createI18n(runtime, { ...options })
* ```
*/
export function createI18n<T extends string>(runtime: Paraglide<T>, options?: I18nUserConfig<T>) {
const translations = options?.pathnames
? resolveUserPathDefinitions(options.pathnames, runtime.availableLanguageTags)
: {}

if (dev) {
const issues = validatePathTranslations(
translations,
runtime.availableLanguageTags,
options?.matchers ?? {}
)
if (issues.length) prettyPrintPathDefinitionIssues(issues)
}

const excludeConfig = options?.exclude ?? []
const defaultLanguageTag = options?.defaultLanguageTag ?? runtime.sourceLanguageTag

const config: I18nConfig<T> = {
defaultLanguageTag,
runtime,
translations,
matchers: options?.matchers ?? {},
exclude: createExclude(excludeConfig),
prefixDefaultLanguage: options?.prefixDefaultLanguage ?? "never",
textDirection: options?.textDirection ?? guessTextDirMap(runtime.availableLanguageTags),
seo: {
noAlternateLinks: options?.seo?.noAlternateLinks ?? false,
},
}

const strategy = PrefixStrategy(
runtime.availableLanguageTags,
defaultLanguageTag,
config.translations,
config.matchers,
config.prefixDefaultLanguage
)

// We don't want the translations to be mutable
Object.freeze(translations)
Object.freeze(config)

return {
/**
* The configuration that was used to create this i18n instance.
*/
config,

/**
* The routing strategy that's being used.
*
* @private Not part of the public API, may change in non-major versions
*/
strategy,

/**
* Returns a `reroute` hook that applies the path translations to the paths.
* Register it in your `src/hooks.js` file to enable path translations.
*
* @example
* ```ts
* // src/hooks.js
* import { i18n } from "$lib/i18n.js"
* export const reroute = i18n.reroute()
* ```
*/
reroute: () => createReroute(strategy),

/**
* Returns a `handle` hook that set's the correct `lang` attribute
* on the `html` element
*
* SERVER ONLY
*/
handle: () => {
throw new Error(dev ? "`i18n.handle` hook should only be used on the server." : "")
},

/**
* Takes in a URL and returns the language that should be used for it.
*
* @param url
* @returns
*/
getLanguageFromUrl(url: URL): T {
if (config.exclude(url.pathname)) return config.defaultLanguageTag
return strategy.getLanguageFromLocalisedPath(url.pathname) || config.defaultLanguageTag
},

/**
* Takes in a route and returns a translated version of it.
* This is useful for use in `goto` statements and `redirect` calls.
*
* The oposite of `i18n.route()`.
*
* @param canonicalPath The path to translate (eg _/base/about_)
* @param lang The language to translate to - Defaults to the current language
* @returns The translated path (eg _/base/de/ueber-uns_)
*
* @example
* ```ts
* redirect(i18n.resolveRoute("/base/about", "de"))
* ```
*/
resolveRoute(path: string, lang: T | undefined = undefined) {
if (config.exclude(path)) return path

const normalizedBase = normaliseBase(base)
const [canonicalPath, dataSuffix] = parseRoute(path as `/${string}`, normalizedBase)

lang = lang ?? runtime.languageTag()
if (!path.startsWith(normalizedBase)) return path

const localisedPath = strategy.getLocalisedPath(canonicalPath, lang)
return serializeRoute(localisedPath, normalizedBase, dataSuffix)
},

/**
* Takes in a path in one language and returns it's canonical version.
* The oposite of `i18n.resolveRoute()`.
* This is useful for use in:
* - Language Switchers
* - Navigation
*
* @param targetedPathSource The path to translate (eg _/base/de/ueber-uns_)
* @returns The canonical version path (eg _/base/about_)
*
* @example
* ```ts
* <a
* href={i18n.route($page.url.pathname)}
* hreflang="en"
* >
* ```
*/
route(translatedPath: string) {
const normalizedBase = normaliseBase(base)

const [localisedPath, dataSuffix] = parseRoute(translatedPath as `/${string}`, normalizedBase)

const lang = strategy.getLanguageFromLocalisedPath(localisedPath)
const languageTag = lang || config.defaultLanguageTag
const canonicalPath = strategy.getCanonicalPath(localisedPath, languageTag)

return serializeRoute(canonicalPath, normalizedBase, dataSuffix)
},
}
}

function normaliseBase(base: string): NormalizedBase {
if (base === "") return ""
if (base.startsWith("/")) return base as `/${string}`

// this should only be reachable during component initialization
// We can detect this, because base is only ever a relative path during component initialization
return canonicalNormaliseBase(base, new URL(get(page).url))
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createHandle, type HandleOptions } from "./hooks/handle.js"
import { createHandle, type HandleOptions } from "./hooks/handle.server.js"
import { createReroute } from "./hooks/reroute.js"
import { base } from "$app/paths"
import { page } from "$app/stores"
import { get } from "svelte/store"
import { browser, dev } from "$app/environment"
import { dev } from "$app/environment"
import { parseRoute, serializeRoute } from "./utils/route.js"
import {
normaliseBase as canonicalNormaliseBase,
Expand Down Expand Up @@ -228,11 +228,7 @@ export function createI18n<T extends string>(runtime: Paraglide<T>, options?: I1
* SERVER ONLY
*/
handle: (options: HandleOptions = {}) => {
if (!browser) {
//We only want this on the server
return createHandle(strategy, config, options)
}
throw new Error(dev ? "`i18n.handle` hook should only be used on the server." : "")
return createHandle(strategy, config, options)
},

/**
Expand Down
Loading

0 comments on commit 1356a25

Please sign in to comment.