astro native i18n + i18n utils
This commit is contained in:
parent
99ef7634e3
commit
b7ce5b7f20
52
.astro-i18n/generated.d.ts
vendored
52
.astro-i18n/generated.d.ts
vendored
@ -1,52 +0,0 @@
|
||||
type DefaultLangCode = "fr"
|
||||
type SupportedLangCode = "en"
|
||||
type LangCode = DefaultLangCode | SupportedLangCode
|
||||
type RouteUri = | "/articles/[slug]" | "/articles" | "/agments/[slug]" | "/agments" | "/references" | "/veille" | "/" | "/plan-du-site"
|
||||
type RouteParams = {"/articles/[slug]": { "slug": string; }; "/articles": undefined; "/agments/[slug]": { "slug": string; }; "/agments": undefined; "/references": undefined; "/veille": undefined; "/": undefined; "/plan-du-site": undefined; }
|
||||
type TranslationPath = "accueil" | "tagline" | "copyright" | "contact.title" | "contact.email" | "contact.tel" | "contenuVide" | "header.skipLink" | "header.mainNav" | "header.homeLink" | "sitemap" | "prevNext.contenus" | "prevNext.precedent" | "prevNext.suivant" | "article.titre" | "article.tagline" | "article.published" | "meta.publication" | "meta.modification" | "meta.credit" | "fragments.titre" | "fragments.tagline" | "references.titre" | "references.slug" | "references.cta" | "references.tagline" | "veille.titre" | "veille.tagline" | "erreur.introuvable" | "erreur.autre" | "erreur.lienRetour" | "seo.meta.description" | "seo.article.title" | "seo.article.description" | "seo.code.title" | "seo.code.description" | "seo.references.title" | "seo.references.description" | "index.articles.pageName" | "index.articles.subtitle" | "index.fragments.pageName" | "index.fragments.subtitle" | "index.references.pageName" | "index.references.subtitle" | "index.veille.pageName" | "index.veille.subtitle" | "index.title" | "index.subtitle" | "index.quoi" | "index.comment" | "index.opensource" | "index.writing" | "index.latestProjects" | "index.latestArticles" | "index.allProjects" | "index.allArticles" | "index.latestSnippets" | "index.allSnippets" | "index.toc" | "contact.contenuVide"
|
||||
type TranslationOptions = { "accueil": {} | undefined; "tagline": {} | undefined; "copyright": {} | undefined; "contact.title": {} | undefined; "contact.email": {} | undefined; "contact.tel": {} | undefined; "contenuVide": {} | undefined; "header.skipLink": {} | undefined; "header.mainNav": {} | undefined; "header.homeLink": {} | undefined; "sitemap": {} | undefined; "prevNext.contenus": {} | undefined; "prevNext.precedent": {} | undefined; "prevNext.suivant": {} | undefined; "article.titre": {} | undefined; "article.tagline": {} | undefined; "article.published": { datetime: unknown; options: unknown; }; "meta.publication": {} | undefined; "meta.modification": {} | undefined; "meta.credit": {} | undefined; "fragments.titre": {} | undefined; "fragments.tagline": {} | undefined; "references.titre": {} | undefined; "references.slug": {} | undefined; "references.cta": {} | undefined; "references.tagline": {} | undefined; "veille.titre": {} | undefined; "veille.tagline": {} | undefined; "erreur.introuvable": {} | undefined; "erreur.autre": {} | undefined; "erreur.lienRetour": {} | undefined; "seo.meta.description": {} | undefined; "seo.article.title": {} | undefined; "seo.article.description": {} | undefined; "seo.code.title": {} | undefined; "seo.code.description": {} | undefined; "seo.references.title": {} | undefined; "seo.references.description": {} | undefined; "index.articles.pageName": {} | undefined; "index.articles.subtitle": {} | undefined; "index.fragments.pageName": {} | undefined; "index.fragments.subtitle": {} | undefined; "index.references.pageName": {} | undefined; "index.references.subtitle": {} | undefined; "index.veille.pageName": {} | undefined; "index.veille.subtitle": {} | undefined; "index.title": {} | undefined; "index.subtitle": {} | undefined; "index.quoi": {} | undefined; "index.comment": {} | undefined; "index.opensource": {} | undefined; "index.writing": {} | undefined; "index.latestProjects": {} | undefined; "index.latestArticles": {} | undefined; "index.allProjects": {} | undefined; "index.allArticles": {} | undefined; "index.latestSnippets": {} | undefined; "index.allSnippets": {} | undefined; "index.toc": {} | undefined; "contact.contenuVide": {} | undefined; }
|
||||
|
||||
declare module "astro-i18n" {
|
||||
export * from "astro-i18n/"
|
||||
|
||||
export function l<Uri extends RouteUri>(
|
||||
route: Uri | string & {},
|
||||
...args: Uri extends keyof RouteParams
|
||||
? undefined extends RouteParams[Uri]
|
||||
? [params?: Record<string, string>, targetLangCode?: LangCode, routeLangCode?: LangCode]
|
||||
: [params: RouteParams[Uri], targetLangCode?: LangCode, routeLangCode?: LangCode]
|
||||
: [params?: Record<string, string>, targetLangCode?: LangCode, routeLangCode?: LangCode]
|
||||
): string
|
||||
|
||||
export function t<Path extends TranslationPath>(
|
||||
path: Path | string & {},
|
||||
...args: undefined extends TranslationOptions[Path]
|
||||
? [options?: keyof TranslationOptions extends Path ? Record<string, unknown> : TranslationOptions[Path], langCode?: LangCode]
|
||||
: [options: TranslationOptions[Path], langCode?: LangCode]
|
||||
): string
|
||||
|
||||
export function extractRouteLangCode(route: string): LangCode | undefined
|
||||
|
||||
type Translation = string | { [translationKey: string]: string | Translation }
|
||||
type Translations = { [langCode: string]: Record<string, Translation> }
|
||||
type RouteTranslations = { [langCode: string]: Record<string, string> }
|
||||
type InterpolationFormatter = (value: unknown, ...args: unknown[]) => string
|
||||
class AstroI18n {
|
||||
defaultLangCode: DefaultLangCode
|
||||
supportedLangCodes: SupportedLangCode[]
|
||||
showDefaultLangCode: boolean
|
||||
translations: Translations
|
||||
routeTranslations: RouteTranslations
|
||||
get langCodes(): LangCode[]
|
||||
get langCode(): LangCode
|
||||
set langCode(langCode: LangCode)
|
||||
get formatters(): Record<string, InterpolationFormatter>
|
||||
init(Astro: { url: URL }, formatters?: Record<string, InterpolationFormatter>): void
|
||||
addTranslations(translations: Translations): void
|
||||
addRouteTranslations(routeTranslations: RouteTranslations): void
|
||||
getFormatter(name: string): InterpolationFormatter | undefined
|
||||
setFormatter(name: string, formatter: InterpolationFormatter): void
|
||||
deleteFormatter(name: string): void
|
||||
}
|
||||
export const astroI18n: AstroI18n
|
||||
}
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
**/*.d.ts
|
||||
eslintrc-auto-import.mjs
|
34
.prettierrc.mjs
Normal file
34
.prettierrc.mjs
Normal file
@ -0,0 +1,34 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
|
||||
const config = {
|
||||
arrowParens: 'always',
|
||||
bracketSameLine: false,
|
||||
bracketSpacing: true,
|
||||
embeddedLanguageFormatting: 'auto',
|
||||
endOfLine: 'lf',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
insertPragma: false,
|
||||
jsxSingleQuote: true,
|
||||
printWidth: 80,
|
||||
proseWrap: 'preserve',
|
||||
quoteProps: 'as-needed',
|
||||
requirePragma: false,
|
||||
semi: false,
|
||||
singleAttributePerLine: false,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'none',
|
||||
useTabs: true,
|
||||
vueIndentScriptAndStyle: false,
|
||||
plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-astro'],
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default config
|
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
22
.vscode/settings.json
vendored
22
.vscode/settings.json
vendored
@ -1,17 +1,7 @@
|
||||
{
|
||||
"cssvar.files": [
|
||||
"./node_modules/open-props/open-props.min.css",
|
||||
// if you have an alternative path to where your styles are located
|
||||
// you can add it in this array of files
|
||||
"assets/styles/variables.css"
|
||||
],
|
||||
|
||||
// Do not ignore node_modules css files, which is ignored by default
|
||||
"cssvar.ignore": [],
|
||||
|
||||
// add support for autocomplete in JS or JS like files
|
||||
"cssvar.extensions": [
|
||||
"css", "jsx", "tsx"
|
||||
],
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.insertSpaces": false,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ export default defineConfig({
|
||||
site: "https://www.nardu.in",
|
||||
build: {
|
||||
format: "directory",
|
||||
},
|
||||
i18n: {
|
||||
locales: ["fr", "en"],
|
||||
defaultLocale: "fr",
|
||||
},
|
||||
image: {
|
||||
domains: ["assets.nardu.in"],
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { defineAstroI18nConfig } from "astro-i18n";
|
||||
|
||||
export default defineAstroI18nConfig({
|
||||
defaultLangCode: "fr",
|
||||
supportedLangCodes: ["en"],
|
||||
showDefaultLangCode: false,
|
||||
trailingSlash: "never",
|
||||
translations: {
|
||||
fr: "src/i18n/fr.json",
|
||||
en: "src/i18n/en.json",
|
||||
},
|
||||
routeTranslations: {
|
||||
en: {
|
||||
"sci-hub-blocage": "sci-hub-unblock",
|
||||
fragments: "snippets",
|
||||
"plan-du-site": "sitemap",
|
||||
references: "work",
|
||||
},
|
||||
},
|
||||
});
|
10
package.json
10
package.json
@ -16,11 +16,15 @@
|
||||
"@astrojs/mdx": "4.0.3",
|
||||
"@astrojs/rss": "4.0.10",
|
||||
"@astrojs/sitemap": "3.2.1",
|
||||
"@astrojs/ts-plugin": "^1.10.4",
|
||||
"astro": "5.1.1",
|
||||
"sharp": "^0.33.4"
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38"
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-organize-imports": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,12 @@
|
||||
---
|
||||
import { l, astroI18n } from "astro-i18n";
|
||||
|
||||
// get all the locales available on the website and remove the one currently in use
|
||||
const availableLocales = astroI18n.langCodes.filter(
|
||||
(locale) => locale !== astroI18n.langCode
|
||||
);
|
||||
// current path
|
||||
const currentRoute = Astro.url.pathname;
|
||||
|
||||
function localeName(locale) {
|
||||
let localeName = "";
|
||||
switch (locale) {
|
||||
case "fr":
|
||||
localeName = "Français";
|
||||
break;
|
||||
case "en":
|
||||
localeName = "English";
|
||||
break;
|
||||
}
|
||||
return localeName;
|
||||
}
|
||||
import { languages } from '../i18n/ui'
|
||||
---
|
||||
|
||||
<ul role="list">
|
||||
<ul>
|
||||
{
|
||||
// create a list of available alternative locale
|
||||
availableLocales.map((locale) => (
|
||||
Object.entries(languages).map(([lang, label]) => (
|
||||
<li>
|
||||
<a
|
||||
href={l(currentRoute as any, {}, locale as any)}
|
||||
class="clean-link nice-link"
|
||||
>
|
||||
{localeName(locale)}
|
||||
</a>
|
||||
<a href={`/${lang}/`}>{label}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
|
150
src/i18n/ui.ts
Normal file
150
src/i18n/ui.ts
Normal file
@ -0,0 +1,150 @@
|
||||
export const languages = {
|
||||
en: 'English',
|
||||
fr: 'Français'
|
||||
}
|
||||
|
||||
export const defaultLang = 'fr'
|
||||
|
||||
export const ui = {
|
||||
en: {
|
||||
accueil: 'home',
|
||||
tagline: 'Freelance web developer specialized in accessibility.',
|
||||
copyright: '(re)Made with Astro',
|
||||
contact: {
|
||||
title: 'contact',
|
||||
email: 'Send me an email (open in application).',
|
||||
tel: 'Call or text me (open in application).',
|
||||
contenuVide: 'Soon: really nice content.'
|
||||
},
|
||||
header: {
|
||||
skipLink: 'Skip to content',
|
||||
mainNav: 'Main menu',
|
||||
homeLink: 'Back to homepage'
|
||||
},
|
||||
sitemap: 'Site map',
|
||||
prevNext: {
|
||||
contenus: 'Similar content',
|
||||
precedent: 'Previous',
|
||||
suivant: 'Next'
|
||||
},
|
||||
article: {
|
||||
titre: 'articles',
|
||||
tagline: 'I blog, sometimes.',
|
||||
published: 'Published on {datetime|date(options)}'
|
||||
},
|
||||
meta: {
|
||||
publication: 'Published on',
|
||||
modification: 'Last updated on',
|
||||
credit: 'Image by'
|
||||
},
|
||||
fragments: {
|
||||
titre: 'snippets',
|
||||
tagline: 'School with Nicool.'
|
||||
},
|
||||
references: {
|
||||
titre: 'work',
|
||||
slug: 'work',
|
||||
cta: 'Visit website',
|
||||
tagline: 'Some work.'
|
||||
},
|
||||
veille: {
|
||||
titre: 'Around the web',
|
||||
tagline: 'Some links that interested me.'
|
||||
},
|
||||
erreur: {
|
||||
introuvable: 'Sorry, page not found.',
|
||||
autre: 'Oups… sorry about that.',
|
||||
lienRetour: 'Back to the home page'
|
||||
},
|
||||
seo: {
|
||||
meta: {
|
||||
description:
|
||||
'Web developer specialized in accessibility and eco-design in Toulouse, France. Development of custom websites, RGAA compliance, maintenance, etc.'
|
||||
},
|
||||
article: {
|
||||
title: 'Articles',
|
||||
description:
|
||||
'A few articles about graphic design and front-end development.'
|
||||
},
|
||||
code: {
|
||||
title: 'Snippets',
|
||||
description: 'Snippets of fresh, easy and accessible code.'
|
||||
},
|
||||
references: {
|
||||
title: 'Work',
|
||||
description: 'A few case studies I worked on as a front-end developer.'
|
||||
}
|
||||
}
|
||||
},
|
||||
fr: {
|
||||
accueil: 'accueil',
|
||||
tagline: 'Développeur web spécialisé en accessibilité.',
|
||||
copyright: '(re)Fait avec Astro',
|
||||
contact: {
|
||||
title: 'contact',
|
||||
email: 'Envoyez-moi un mail (ouverture du logiciel automatique).',
|
||||
tel: 'Contactez-moi par téléphone (ouverture du logiciel automatique).'
|
||||
},
|
||||
contenuVide: 'Bientôt ici : du contenu de qualité',
|
||||
header: {
|
||||
skipLink: 'Accéder au contenu',
|
||||
mainNav: 'Menu principal',
|
||||
homeLink: 'Accueil du site'
|
||||
},
|
||||
sitemap: 'Plan du site',
|
||||
prevNext: {
|
||||
contenus: 'Contenus similaires',
|
||||
precedent: 'Précédent',
|
||||
suivant: 'Suivant'
|
||||
},
|
||||
article: {
|
||||
titre: 'articles',
|
||||
tagline: 'Je blog, un peu.',
|
||||
published: 'Publié le {datetime|date(options)}'
|
||||
},
|
||||
meta: {
|
||||
publication: 'Publié le',
|
||||
modification: 'Mis à jour le',
|
||||
credit: 'Image par'
|
||||
},
|
||||
fragments: {
|
||||
titre: 'fragments',
|
||||
tagline: 'Les tutos de Nico mdr.'
|
||||
},
|
||||
references: {
|
||||
titre: 'références',
|
||||
slug: 'references',
|
||||
cta: 'Consulter le site',
|
||||
tagline: 'Quelques références.'
|
||||
},
|
||||
veille: {
|
||||
titre: 'veille',
|
||||
tagline: 'Des liens, en vrac.'
|
||||
},
|
||||
erreur: {
|
||||
introuvable: 'Page introuvable',
|
||||
autre: 'Oups… désolé pour cette erreur.',
|
||||
lienRetour: 'Retour à l’accueil'
|
||||
},
|
||||
seo: {
|
||||
meta: {
|
||||
description:
|
||||
'Développeur web spécialisé en accessibilité numérique et éco-conception à Toulouse. Création de sites web sur mesure, mise en conformité RGAA, maintenance, etc.'
|
||||
},
|
||||
article: {
|
||||
title: 'Articles',
|
||||
description:
|
||||
"Quelques articles sur le développement web front-end et l'informatique à Toulouse."
|
||||
},
|
||||
code: {
|
||||
title: 'Fragments',
|
||||
description: 'Fragments de codes stylés, faciles et accessibles.'
|
||||
},
|
||||
references: {
|
||||
title: 'Références',
|
||||
description:
|
||||
'Quelques travaux réalisés en tant que et développeur web front-end à Toulouse.'
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
import { astroI18n } from "astro-i18n";
|
||||
astroI18n.init(Astro);
|
||||
const locale = Astro.currentLocale;
|
||||
|
||||
import "../styles/style.css";
|
||||
|
||||
@ -11,7 +10,7 @@ import Footer from "../components/Footer.astro";
|
||||
const { pageTitle } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang={astroI18n.langCode} dir="ltr">
|
||||
<html lang={locale} dir="ltr">
|
||||
<Head pageTitle={pageTitle} />
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
|
@ -1,33 +1,27 @@
|
||||
---
|
||||
import { l, t, astroI18n } from "astro-i18n";
|
||||
// import AstroImage from "../components/AstroImage.astro";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||
import { getLangFromUrl, useTranslations } from '../utils/i18n'
|
||||
|
||||
const { frontmatter, image, title } = Astro.props;
|
||||
const publishedDate = new Date(frontmatter.pubDate);
|
||||
const localizedDate = new Intl.DateTimeFormat(astroI18n.langCode, {
|
||||
dateStyle: "long",
|
||||
}).format(publishedDate);
|
||||
const locale = getLangFromUrl(Astro.url)
|
||||
const t = useTranslations(locale)
|
||||
|
||||
const { frontmatter, image, title } = Astro.props
|
||||
const publishedDate = new Date(frontmatter.pubDate)
|
||||
const localizedDate = new Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'long'
|
||||
}).format(publishedDate)
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={title}>
|
||||
<p>
|
||||
Publié le : <time datetime={frontmatter.pubDate}>
|
||||
{t('article.published')} : <time datetime={frontmatter.pubDate}>
|
||||
{localizedDate}.
|
||||
</time>
|
||||
</p>
|
||||
<p>
|
||||
{
|
||||
t("article.published", {
|
||||
datetime: frontmatter.pubDate,
|
||||
options: { dateStyle: "long" },
|
||||
})
|
||||
}
|
||||
</p>
|
||||
<div class="tags">
|
||||
<div class='tags'>
|
||||
{
|
||||
frontmatter.tags.map((tag) => (
|
||||
<p class="tag">
|
||||
<p class='tag'>
|
||||
<a href={l(`/tags/${[tag]}`)}>{tag}</a>
|
||||
</p>
|
||||
))
|
||||
|
@ -1,8 +1,5 @@
|
||||
---
|
||||
// init i18n
|
||||
import { getLocale } from "astro-i18n-aut";
|
||||
|
||||
const locale = getLocale(Astro.url);
|
||||
const locale = Astro.currentLocale;
|
||||
|
||||
// import stuff
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
|
28
src/utils/i18n.ts
Normal file
28
src/utils/i18n.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { defaultLang, ui } from '../i18n/ui'
|
||||
|
||||
export function getLangFromUrl(url: URL) {
|
||||
const [, lang] = url.pathname.split('/')
|
||||
if (lang in ui) return lang as keyof typeof ui
|
||||
return defaultLang
|
||||
}
|
||||
|
||||
type NestedKeyOf<T> = {
|
||||
[K in keyof T]: T[K] extends object
|
||||
? `${K & string}.${NestedKeyOf<T[K]> & string}`
|
||||
: K & string
|
||||
}[keyof T]
|
||||
|
||||
export function useTranslations(lang: keyof typeof ui) {
|
||||
return function t(key: NestedKeyOf<(typeof ui)[typeof defaultLang]>) {
|
||||
const keys = key.split('.')
|
||||
let value = ui[lang]
|
||||
let fallback = ui[defaultLang]
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k]
|
||||
fallback = fallback?.[k]
|
||||
}
|
||||
|
||||
return value || fallback
|
||||
}
|
||||
}
|
@ -1,8 +1,18 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@components/*": ["src/components/*"],
|
||||
"@layouts/*": ["src/layouts/*"]
|
||||
},
|
||||
"strictNullChecks": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@astrojs/ts-plugin"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user