Adjust in i18n

This commit is contained in:
Leonardo Murça 2025-06-03 10:32:22 -03:00
parent c573d8c2c6
commit f1bdb9bf98
18 changed files with 126 additions and 75 deletions

21
.gitignore vendored
View file

@ -23,3 +23,24 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
/.vscode /.vscode
.DS_Store
node_modules
dist
test-results/
package-lock.json
yarn.lock
vite.config.js.timestamp-*
/packages/create-svelte/template/CHANGELOG.md
/packages/package/test/**/package
/documentation/types.js
.env
.vercel_build_output
.svelte-kit
.cloudflare
.pnpm-debug.log
.netlify
.turbo
.vercel
.test-tmp
symlink-from
.idea/

View file

@ -9,7 +9,7 @@ export const nodes = [
() => import('./nodes/5') () => import('./nodes/5')
]; ];
export const server_loads = []; export const server_loads = [0];
export const dictionary = { export const dictionary = {
"/": [2], "/": [2],

View file

@ -1,3 +1 @@
import * as universal from "../../../../src/routes/+layout.js";
export { universal };
export { default as component } from "../../../../src/routes/+layout.svelte"; export { default as component } from "../../../../src/routes/+layout.svelte";

View file

@ -21,7 +21,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n <head>\n <style>\n :root {\n font-family: Inter, Avenir, Helvetica, Arial, sans-serif;\n font-size: 16px;\n line-height: 24px;\n font-weight: 400;\n font-synthesis: none;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n -webkit-text-size-adjust: 100%;\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n display: flex;\n justify-content: center;\n flex-direction: column;\n margin: 0;\n width: 100%;\n height: 100%;\n }\n\n #app {\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n background-color: #f2f6f5;\n z-index: 10;\n }\n\n input[type='submit'] {\n width: 100%;\n font-size: 20px;\n margin-top: 20px;\n background-color: #05345f;\n font-weight: 700;\n color: white;\n padding: 10px;\n -webkit-appearance: none;\n border-radius: 0;\n }\n\n input[type='submit']:hover {\n cursor: pointer;\n background-color: black;\n color: white;\n }\n\n body a {\n text-decoration: none;\n color: #06345f;\n border-bottom: 3px solid #06345f;\n }\n\n body a:hover {\n background-color: #06345f;\n color: #ffffff;\n }\n\n :is(h1, h2, h3, h4, h5, h6) {\n color: #06345f;\n }\n\n strong {\n color: #06345f;\n }\n\n ul li::marker {\n color: #06345f;\n }\n </style>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n", app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n <head>\n <style>\n :root {\n font-family: Inter, Avenir, Helvetica, Arial, sans-serif;\n font-size: 16px;\n line-height: 24px;\n font-weight: 400;\n font-synthesis: none;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n -webkit-text-size-adjust: 100%;\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n display: flex;\n justify-content: center;\n flex-direction: column;\n margin: 0;\n width: 100%;\n height: 100%;\n }\n\n #app {\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n background-color: #f2f6f5;\n z-index: 10;\n }\n\n input[type='submit'] {\n width: 100%;\n font-size: 20px;\n margin-top: 20px;\n background-color: #05345f;\n font-weight: 700;\n color: white;\n padding: 10px;\n -webkit-appearance: none;\n border-radius: 0;\n }\n\n input[type='submit']:hover {\n cursor: pointer;\n background-color: black;\n color: white;\n }\n\n body a {\n text-decoration: none;\n color: #06345f;\n border-bottom: 3px solid #06345f;\n }\n\n body a:hover {\n background-color: #06345f;\n color: #ffffff;\n }\n\n :is(h1, h2, h3, h4, h5, h6) {\n color: #06345f;\n }\n\n strong {\n color: #06345f;\n }\n\n ul li::marker {\n color: #06345f;\n }\n </style>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n" error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
}, },
version_hash: "lnkeyd" version_hash: "1f4q059"
}; };
export async function get_hooks() { export async function get_hooks() {

View file

@ -1,15 +1,15 @@
{ {
"/": [ "/": [
"src/routes/+layout.js", "src/routes/+layout.server.js",
"src/routes/+layout.js" "src/routes/+layout.server.js"
], ],
"/about": [ "/about": [
"src/routes/+layout.js" "src/routes/+layout.server.js"
], ],
"/donate": [ "/donate": [
"src/routes/+layout.js" "src/routes/+layout.server.js"
], ],
"/viewer": [ "/viewer": [
"src/routes/+layout.js" "src/routes/+layout.server.js"
] ]
} }

View file

@ -14,13 +14,15 @@ export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageParentData = EnsureDefined<LayoutData>; type PageParentData = EnsureDefined<LayoutData>;
type LayoutRouteId = RouteId | "/" | "/about" | "/donate" | "/viewer" | null type LayoutRouteId = RouteId | "/" | "/about" | "/donate" | "/viewer" | null
type LayoutParams = RouteParams & { } type LayoutParams = RouteParams & { }
type LayoutServerParentData = EnsureDefined<{}>;
type LayoutParentData = EnsureDefined<{}>; type LayoutParentData = EnsureDefined<{}>;
export type PageServerData = null; export type PageServerData = null;
export type PageData = Expand<PageParentData>; export type PageData = Expand<PageParentData>;
export type PageProps = { data: PageData } export type PageProps = { data: PageData }
export type LayoutServerData = null; export type LayoutServerLoad<OutputData extends OutputDataShape<LayoutServerParentData> = OutputDataShape<LayoutServerParentData>> = Kit.ServerLoad<LayoutParams, LayoutServerParentData, OutputData, LayoutRouteId>;
export type LayoutLoad<OutputData extends OutputDataShape<LayoutParentData> = OutputDataShape<LayoutParentData>> = Kit.Load<LayoutParams, LayoutServerData, LayoutParentData, OutputData, LayoutRouteId>; export type LayoutServerLoadEvent = Parameters<LayoutServerLoad>[0];
export type LayoutLoadEvent = Parameters<LayoutLoad>[0]; export type LayoutServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.server.js').load>>>>>>;
export type LayoutData = Expand<Omit<LayoutParentData, keyof Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>> & OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>>>>; export type LayoutData = Expand<Omit<LayoutParentData, keyof LayoutServerData> & EnsureDefined<LayoutServerData>>;
export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet } export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View file

@ -1,17 +0,0 @@
// @ts-nocheck
import { loadTranslations } from '$lib/translations';
/** */
export const load = async () => {
const initLocale = primaryLanguage(navigator.language) || 'en';
// TODO: Fix the undefined location issue
await loadTranslations(initLocale);
return {};
};
const primaryLanguage = (/** @type {string} */ locale) => {
if (!locale) return '';
return locale.split('-')[0];
};

7
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "embroidery-viewer", "name": "embroidery-viewer",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"accept-language-parser": "^1.5.0",
"sveltekit-i18n": "^2.4.2" "sveltekit-i18n": "^2.4.2"
}, },
"devDependencies": { "devDependencies": {
@ -1165,6 +1166,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/accept-language-parser": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz",
"integrity": "sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw==",
"license": "MIT"
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",

View file

@ -31,6 +31,7 @@
"vite": "^6.2.6" "vite": "^6.2.6"
}, },
"dependencies": { "dependencies": {
"accept-language-parser": "^1.5.0",
"sveltekit-i18n": "^2.4.2" "sveltekit-i18n": "^2.4.2"
} }
} }

View file

@ -1,5 +1,5 @@
<script> <script>
import { t, locale, locales } from '$lib/translations'; import { t, locale, locales, SUPPORTED_LOCALES } from '$lib/translations';
import logo from '$lib/assets/logo.webp'; import logo from '$lib/assets/logo.webp';
import MediaQuery from './MediaQuery.svelte'; import MediaQuery from './MediaQuery.svelte';
@ -10,7 +10,10 @@
}; };
const onSwitchToOppositeLang = () => { const onSwitchToOppositeLang = () => {
$locale = $locale === 'en' ? 'pt' : 'en'; $locale =
$locale === SUPPORTED_LOCALES.EN_US
? SUPPORTED_LOCALES.PT_BR
: SUPPORTED_LOCALES.EN_US;
}; };
let isMenuOpen = false; let isMenuOpen = false;
@ -59,7 +62,7 @@
</nav> </nav>
<a <a
class="common-switch {$locale === 'en' class="common-switch {$locale === SUPPORTED_LOCALES.EN_US
? 'portuguese-switch' ? 'portuguese-switch'
: 'english-switch'}" : 'english-switch'}"
href="#" href="#"

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -1,22 +1,33 @@
import i18n from 'sveltekit-i18n'; import i18n from 'sveltekit-i18n';
import lang from './lang.json';
/**
* A frozen object mapping locale identifiers to their respective locale codes.
*
* These values represent the supported languages in the application.
* Used for validating user preferences and loading the correct translations.
*
* @readonly
* @enum {string}
*/
export const SUPPORTED_LOCALES = Object.freeze({
EN_US: 'en-US',
PT_BR: 'pt-BR',
});
/** @type {import('sveltekit-i18n').Config} */ /** @type {import('sveltekit-i18n').Config} */
const config = { const config = {
translations: { initLocale: navigator.language,
en: { lang }, fallbackLocale: SUPPORTED_LOCALES.PT_BR,
pt: { lang },
},
loaders: [ loaders: [
{ {
locale: 'en', locale: SUPPORTED_LOCALES.EN_US,
key: 'header', key: 'header',
loader: async () => (await import('./en/header.json')).default, loader: async () => (await import('./en-US/header.json')).default,
}, },
{ {
locale: 'pt', locale: SUPPORTED_LOCALES.PT_BR,
key: 'header', key: 'header',
loader: async () => (await import('./pt/header.json')).default, loader: async () => (await import('./pt-BR/header.json')).default,
}, },
], ],
}; };
@ -25,17 +36,8 @@ export const { t, locale, locales, loading, loadTranslations } = new i18n(
config, config,
); );
// Save to localStorage on change
locale.subscribe(($locale) => { locale.subscribe(($locale) => {
if (typeof localStorage !== 'undefined') { if (typeof document !== 'undefined') {
localStorage.setItem('locale', $locale); document.cookie = `locale=${$locale}; path=/; SameSite=Strict;`;
} }
}); });
// Load from localStorage on initialization
if (typeof localStorage !== 'undefined') {
const savedLocale = localStorage.getItem('locale');
if (savedLocale && savedLocale !== 'null') {
locale.set(savedLocale);
}
}

View file

@ -1,4 +0,0 @@
{
"en": "English",
"pt": "Português"
}

View file

@ -1,16 +0,0 @@
import { loadTranslations } from '$lib/translations';
/** @type {import('@sveltejs/kit').Load} */
export const load = async () => {
const initLocale = primaryLanguage(navigator.language) || 'en';
// TODO: Fix the undefined location issue
await loadTranslations(initLocale);
return {};
};
const primaryLanguage = (/** @type {string} */ locale) => {
if (!locale) return '';
return locale.split('-')[0];
};

View file

@ -0,0 +1,51 @@
import { parse } from 'accept-language-parser';
import { loadTranslations } from '$lib/translations';
import { SUPPORTED_LOCALES } from '$lib/translations';
/**
* A set of all supported locale codes, used to validate and match against
* user preferences from cookies or Accept-Language headers. We're using a
* Set for better performance in lookup.
*
* Example values: "en-US", "pt-BR"
* @type {Set<string>}
*/
const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES));
/**
* Returns a valid locale from cookies, or null if not valid/found.
* @param {{ get: (cookies: string) => any; }} cookies
*/
function localeFromCookies(cookies) {
const locale = cookies.get('locale');
return locale && SUPPORTED_LOCALE_SET.has(locale) ? locale : null;
}
/**
* Parses the Accept-Language header and returns the best matching locale.
* @param {string | null | undefined} header
*/
function localeFromHeader(header) {
if (!header) return null;
const parsedLanguages = parse(header);
for (const { code, region } of parsedLanguages) {
const locale = region ? `${code}-${region}` : code;
if (SUPPORTED_LOCALE_SET.has(locale)) {
return locale;
}
}
return null;
}
/** @type {import('@sveltejs/kit').ServerLoad}*/
export async function load({ request, cookies }) {
const cookieLocale = localeFromCookies(cookies);
const headerLocale = localeFromHeader(request.headers.get('accept-language'));
const language = cookieLocale || headerLocale || SUPPORTED_LOCALES.EN_US;
await loadTranslations(language);
return { language };
}

View file

@ -1,5 +1,9 @@
<script> <script>
import { locale } from '$lib/translations';
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
export let data;
$: locale.set(data.language);
</script> </script>
<Header /> <Header />