Creating lang route, moving files under, setting up the i18n with en and es, and starting the message transition.

This commit is contained in:
Bradley Shellnut 2024-01-17 16:34:33 -08:00
parent 5241572b76
commit e2a6a38ef5
32 changed files with 201 additions and 26 deletions

View file

@ -7,6 +7,7 @@
"Mullvad", "Mullvad",
"nextjs", "nextjs",
"Obispo", "Obispo",
"paraglide",
"selfhosting", "selfhosting",
"Syncthing", "Syncthing",
"Wallabag" "Wallabag"

18
languages/en.json Normal file
View file

@ -0,0 +1,18 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"home_title": "Hello! I'm Bradley Shellnut.",
"home_about": "I'm a full stack software engineer currently working on Java Spring, PostgreSQL, and React / Angular JS.",
"home_learning": "At home you can usually find me learning new things and working with SvelteKit, Next.js, and Gatsby.",
"nav_home": "Home",
"nav_about": "About",
"nav_about_link": "about",
"nav_uses": "Uses",
"nav_uses_link": "uses",
"nav_articles": "Articles",
"nav_articles_link": "articles",
"nav_portfolio": "Portfolio",
"nav_portfolio_link": "portfolio",
"nav_privacy": "Privacy",
"nav_privacy_link": "privacy",
"about_whoami": "Hey! My name is Bradley Shellnut."
}

18
languages/es.json Normal file
View file

@ -0,0 +1,18 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"home_title": "!Hola! Me llamo Bradley Shellnut.",
"home_about": "Soy un ingeniero de software de pila completa que actualmente trabaja en Java Spring, PostgreSQL y React / Angular JS.",
"home_learning": "En casa por lo general me puedes encontrar aprendiendo cosas nuevas y trabajando con SvelteKit, Next.js, y Gatsby.",
"nav_home": "Inicio",
"nav_about": "Acerca de",
"nav_about_link": "acerca-de",
"nav_uses": "Utiliza",
"nav_uses_link": "utiliza",
"nav_articles": "Artículos",
"nav_articles_link": "articulos",
"nav_portfolio": "Cartera",
"nav_portfolio_link": "cartera",
"nav_privacy": "Privacidad",
"nav_privacy_link": "privacidad",
"about_whoami": "¡Hola! Me llamo Bradley Shellnut."
}

View file

@ -10,6 +10,8 @@
"test:ui": "svelte-kit sync && playwright test --ui", "test:ui": "svelte-kit sync && playwright test --ui",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"compile": "paraglide-js compile --project ./project.inlang",
"watch": "paraglide-js compile --project ./project.inlang --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .", "format": "prettier --plugin-search-dir . --write .",
"test:integration": "playwright test", "test:integration": "playwright test",

View file

@ -2,7 +2,7 @@
"$schema": "https://inlang.com/schema/project-settings", "$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en", "sourceLanguageTag": "en",
"languageTags": [ "languageTags": [
"en" "en", "es"
], ],
"modules": [ "modules": [
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
@ -14,6 +14,6 @@
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js" "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
], ],
"plugin.inlang.messageFormat": { "plugin.inlang.messageFormat": {
"pathPattern": "./messages/{languageTag}.json" "pathPattern": "./languages/{languageTag}.json"
} }
} }

View file

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <!-- Added Placeholders for lang & dir attributes. These get replaced in `hooks.server.ts` -->
<html lang="%lang%" dir="%textDir%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/b_shell_nut_favicon.gif" /> <link rel="icon" href="%sveltekit.assets%/b_shell_nut_favicon.gif" />

21
src/hooks.server.ts Normal file
View file

@ -0,0 +1,21 @@
import { getTextDirection } from "$lib/i18n"
import { sourceLanguageTag, type AvailableLanguageTag } from "$paraglide/runtime"
/*
We set the `lang` and `dir` attributes on the `<html>` element using a hook.
the `app.html` file contains placeholders for these attributes, which we just find and replace.
*/
export async function handle({ event, resolve }) {
const lang: AvailableLanguageTag =
(event.params.lang as AvailableLanguageTag) ?? sourceLanguageTag
const textDirection = getTextDirection(lang)
return await resolve(event, {
transformPageChunk({ done, html }) {
if (done) {
return html.replace("%lang%", lang).replace("%textDir%", textDirection)
}
},
})
}

20
src/hooks.ts Normal file
View file

@ -0,0 +1,20 @@
import type { Reroute } from '@sveltejs/kit';
const translated: Record<string, string> = {
'/en/about': '/en/about',
'/en/uses': '/en/uses',
'/en/articles': '/en/articles',
'/en/portfolio': '/en/portfolio',
'/en/privacy': '/en/privacy',
'/es/acerca-de': '/es/about',
'/es/utiliza': '/es/uses',
'/es/articulos': '/es/articles',
'/es/cartera': '/es/portfolio',
'/es/privacidad': '/es/privacy'
};
export const reroute: Reroute = ({ url }) => {
if (url.pathname in translated) {
return translated[url.pathname];
}
};

View file

@ -1,28 +1,40 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { translatePath } from '$lib/i18n';
import { availableLanguageTags, languageTag } from '$paraglide/runtime';
import * as m from "$paraglide/messages";
$: pathname = $page.url.pathname;
$: lang = languageTag();
</script> </script>
<header aria-label="header navigation"> <header aria-label="header navigation">
<nav> <nav>
<a href="/" class:active={$page.url.pathname === '/'}>Home</a> <a href='/' class:active={pathname === '/'}>{m.nav_home()}</a>
<a <a
href="/about" href={`/${lang}/${m.nav_about_link()}`}
class:active={$page.url.pathname === '/about'} class:active={pathname === `/${m.nav_about_link()}`}
> >
About {m.nav_about()}
</a> </a>
<a <a
href="/portfolio" href={`/${lang}/${m.nav_portfolio_link()}`}
class:active={$page.url.pathname === '/portfolio'} class:active={pathname === `/${m.nav_portfolio_link()}`}
> >
Portfolio {m.nav_portfolio()}
</a> </a>
<a <a
href="/uses" href={`/${lang}/${m.nav_uses_link()}`}
class:active={$page.url.pathname === '/uses'} class:active={pathname === `/${m.nav_uses_link()}`}
> >
Uses {m.nav_uses()}
</a> </a>
<select on:change={(e) => goto(translatePath(pathname, e?.target?.value)) }>
{#each availableLanguageTags as lang}
<option value={lang} selected={lang === languageTag()}>{lang}</option>
{/each}
</select>
</nav> </nav>
</header> </header>

43
src/lib/i18n.ts Normal file
View file

@ -0,0 +1,43 @@
import {
sourceLanguageTag,
availableLanguageTags,
type AvailableLanguageTag,
} from "$paraglide/runtime"
/**
* Takes in a path with or without a language tag and returns the path with the given language tag.
* @returns
*/
export function translatePath(path: string, lang: AvailableLanguageTag) {
path = getPathWithoutLang(path)
// Don't prefix with the source language tag, that's the default
if (lang === sourceLanguageTag) return path
// Otherwise, prefix with the language tag
else return `/${lang}${path}`
}
/**
* Removes the language tag from the path, if it exists.
*/
function getPathWithoutLang(path: string) {
const [_, maybeLang, ...rest] = path.split("/")
if (availableLanguageTags.includes(maybeLang as any)) {
return `/${rest.join("/")}`;
} else {
return path;
}
}
/**
* Look up the text direction for a given locale.
* You could use a Polyfill for `Intl.Locale.prototype.getTextInfo` instead.
*/
export function getTextDirection(locale: AvailableLanguageTag) {
const directions: Record<AvailableLanguageTag, "ltr" | "rtl"> = {
en: "ltr",
es: "ltr",
}
return directions[locale]
}

View file

2
src/params/lang.ts Normal file
View file

@ -0,0 +1,2 @@
import { isAvailableLanguageTag } from "$paraglide/runtime"
export const match = isAvailableLanguageTag

View file

@ -7,9 +7,11 @@
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
import "nprogress/nprogress.css"; import "nprogress/nprogress.css";
import '$root/styles/styles.pcss'; import '$root/styles/styles.pcss';
import { setLanguageTag, sourceLanguageTag, type AvailableLanguageTag, availableLanguageTags } from "$paraglide/runtime";
import Header from '$lib/components/header/index.svelte'; import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer/index.svelte'; import Footer from '$lib/components/footer/index.svelte';
import Analytics from '$lib/components/analytics/index.svelte'; import Analytics from '$lib/components/analytics/index.svelte';
import { getTextDirection, translatePath } from '$lib/i18n';
NProgress.configure({ NProgress.configure({
// Full list: https://github.com/rstacruz/nprogress#configuration // Full list: https://github.com/rstacruz/nprogress#configuration
@ -37,6 +39,26 @@
], ],
...$page.data.metaTagsChild ...$page.data.metaTagsChild
} }
// Determine the current language from the URL. Fall back to the source language if none is specified.
$: lang = $page.params.lang as AvailableLanguageTag ?? sourceLanguageTag
console.log('lang', lang)
// Set the language tag in the Paraglide runtime.
// This determines which language the strings are translated to.
// You should only do this in the template, to avoid concurrent requests interfering with each other.
$: setLanguageTag(lang)
// Determine the text direction of the current language
$: textDirection = getTextDirection(lang)
// Keep the <html> lang and dir attributes in sync with the current language
$: if (browser) {
document.documentElement.dir = textDirection
document.documentElement.lang = lang
}
</script> </script>
{#if !dev} {#if !dev}
@ -45,12 +67,20 @@
<MetaTags {...metaTags} /> <MetaTags {...metaTags} />
<svelte:head>
{#each availableLanguageTags as lang}
<link rel="alternate" hreflang={lang} href={translatePath($page.url.pathname, lang)} />
{/each}
</svelte:head>
<div class="wrapper"> <div class="wrapper">
<Header /> {#key lang}
<main> <Header />
<slot /> <main>
</main> <slot />
<Footer /> </main>
<Footer />
{/key}
</div> </div>
<style lang="postcss"> <style lang="postcss">

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; import type { PageData } from '../$types';
import * as m from "$paraglide/messages";
import Bandcamp from '$lib/components/bandcamp/index.svelte'; import Bandcamp from '$lib/components/bandcamp/index.svelte';
import Articles from '$lib/components/Articles.svelte'; import Articles from '$lib/components/Articles.svelte';
import type { Album } from '$lib/types/album'; import type { Album } from '$lib/types/album';
@ -22,14 +23,13 @@
<div class="home"> <div class="home">
<div> <div>
<h1>Hello! I'm Bradley Shellnut.</h1> <h1>{m.home_title()}</h1>
</div> </div>
<p> <p>
I'm a full stack software engineer currently working on Java Spring, PostgreSQL, and React / Angular JS. {m.home_about()}
</p> </p>
<p> <p>
At home you can usually find me learning new things and working with {m.home_learning()}
SvelteKit, Next.js, and Gatsby.
</p> </p>
<p> <p>
Or you may find me jamming out to music 🎶, hiking ⛰️, making{' '} Or you may find me jamming out to music 🎶, hiking ⛰️, making{' '}

View file

@ -8,6 +8,7 @@
import Remix from '@iconify-icons/simple-icons/remix'; import Remix from '@iconify-icons/simple-icons/remix';
import Svelte from '@iconify-icons/simple-icons/svelte'; import Svelte from '@iconify-icons/simple-icons/svelte';
import TypeScript from '@iconify-icons/simple-icons/typescript'; import TypeScript from '@iconify-icons/simple-icons/typescript';
import * as m from "$paraglide/messages";
import LazyImage from '$lib/components/LazyImage.svelte'; import LazyImage from '$lib/components/LazyImage.svelte';
import antarctica from '$lib/assets/images/antarctica.png?as=run:0'; import antarctica from '$lib/assets/images/antarctica.png?as=run:0';
import tortie_derp from '$lib/assets/images/tortie_derp.jpg?as=run'; import tortie_derp from '$lib/assets/images/tortie_derp.jpg?as=run';
@ -24,7 +25,7 @@
<div class="about"> <div class="about">
<div> <div>
<h1>About</h1> <h1>About</h1>
<p>Hey! My name is Bradley Shellnut.</p> <p>{m.about_whoami()}</p>
<p> <p>
I'm {new Date().getFullYear() - 1991} years old and I am a full stack I'm {new Date().getFullYear() - 1991} years old and I am a full stack
software engineer who's interested in new tech and not afraid to software engineer who's interested in new tech and not afraid to

View file

@ -23,8 +23,14 @@ const config = {
adapter: adapter(), adapter: adapter(),
alias: { alias: {
$root: './src', $root: './src',
$paraglide: './src/paraglide' $paraglide: './src/paraglide/'
} },
// Need for crawling to work until
// https://github.com/sveltejs/kit/issues/11133
// is fixed
prerender: {
entries: ["/"],
},
} }
}; };