chore(i18n): setup i18n plugin config

This commit is contained in:
Corentin Thomasset 2023-06-18 14:11:38 +02:00 committed by Corentin THOMASSET
parent a6bbeaebd8
commit ebfb872fae
11 changed files with 256 additions and 5 deletions

View File

@ -173,6 +173,7 @@
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useI18n": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,

2
auto-imports.d.ts vendored
View File

@ -170,6 +170,7 @@ declare global {
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useI18n: typeof import('vue-i18n')['useI18n']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
@ -459,6 +460,7 @@ declare module 'vue' {
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>

3
locales/en.yml Normal file
View File

@ -0,0 +1,3 @@
home:
categories:
newestTools: "Newest tools"

3
locales/fr.yml Normal file
View File

@ -0,0 +1,3 @@
home:
categories:
newestTools: "Nouveaux outils"

View File

@ -75,6 +75,7 @@
"ua-parser-js": "^1.0.35",
"uuid": "^8.3.2",
"vue": "^3.2.47",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"xml-formatter": "^3.3.2",
"yaml": "^2.2.1"
@ -82,6 +83,7 @@
"devDependencies": {
"@antfu/eslint-config": "^0.39.3",
"@iconify-json/mdi": "^1.1.50",
"@intlify/unplugin-vue-i18n": "^0.11.0",
"@playwright/test": "^1.32.3",
"@rushstack/eslint-patch": "^1.2.0",
"@types/bcryptjs": "^2.4.2",

View File

@ -1,4 +1,8 @@
lockfileVersion: '6.0'
lockfileVersion: '6.1'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@it-tools/bip39':
@ -127,6 +131,9 @@ dependencies:
vue:
specifier: ^3.2.47
version: 3.2.47
vue-i18n:
specifier: ^9.2.2
version: 9.2.2(vue@3.2.47)
vue-router:
specifier: ^4.1.6
version: 4.1.6(vue@3.2.47)
@ -144,6 +151,9 @@ devDependencies:
'@iconify-json/mdi':
specifier: ^1.1.50
version: 1.1.50
'@intlify/unplugin-vue-i18n':
specifier: ^0.11.0
version: 0.11.0(rollup@2.79.1)(vue-i18n@9.2.2)
'@playwright/test':
specifier: ^1.32.3
version: 1.32.3
@ -1731,6 +1741,110 @@ packages:
- supports-color
dev: true
/@intlify/bundle-utils@6.0.1(vue-i18n@9.2.2):
resolution: {integrity: sha512-BkeZNKZiC0B7K3OYMwiPLoAqsZmKH3SxTL75vYAkuQ//XWR8WO0NpfjXhTxgLTVFHxMcNb2agAopC0DP6fqDrg==}
engines: {node: '>= 14.16'}
peerDependencies:
petite-vue-i18n: '*'
vue-i18n: '*'
peerDependenciesMeta:
petite-vue-i18n:
optional: true
vue-i18n:
optional: true
dependencies:
'@intlify/message-compiler': 9.3.0-beta.17
'@intlify/shared': 9.3.0-beta.17
acorn: 8.8.2
escodegen: 2.0.0
estree-walker: 2.0.2
jsonc-eslint-parser: 1.4.1
magic-string: 0.30.0
mlly: 1.2.0
source-map: 0.6.1
vue-i18n: 9.2.2(vue@3.2.47)
yaml-eslint-parser: 0.3.2
dev: true
/@intlify/core-base@9.2.2:
resolution: {integrity: sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==}
engines: {node: '>= 14'}
dependencies:
'@intlify/devtools-if': 9.2.2
'@intlify/message-compiler': 9.2.2
'@intlify/shared': 9.2.2
'@intlify/vue-devtools': 9.2.2
/@intlify/devtools-if@9.2.2:
resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==}
engines: {node: '>= 14'}
dependencies:
'@intlify/shared': 9.2.2
/@intlify/message-compiler@9.2.2:
resolution: {integrity: sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==}
engines: {node: '>= 14'}
dependencies:
'@intlify/shared': 9.2.2
source-map: 0.6.1
/@intlify/message-compiler@9.3.0-beta.17:
resolution: {integrity: sha512-i7hvVIRk1Ax2uKa9xLRJCT57to08OhFMhFXXjWN07rmx5pWQYQ23MfX1xgggv9drnWTNhqEiD+u4EJeHoS5+Ww==}
engines: {node: '>= 14'}
dependencies:
'@intlify/shared': 9.3.0-beta.17
source-map: 0.6.1
dev: true
/@intlify/shared@9.2.2:
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
engines: {node: '>= 14'}
/@intlify/shared@9.3.0-beta.17:
resolution: {integrity: sha512-mscf7RQsUTOil35jTij4KGW1RC9SWQjYScwLxP53Ns6g24iEd5HN7ksbt9O6FvTmlQuX77u+MXpBdfJsGqizLQ==}
engines: {node: '>= 14'}
dev: true
/@intlify/unplugin-vue-i18n@0.11.0(rollup@2.79.1)(vue-i18n@9.2.2):
resolution: {integrity: sha512-ivcLZo08fvepHWV8o5lcKfhcKFSWqhwrqIAU6pUIbvq2ICo9fnXnIPYIZj7FeuHDLW1G3ADm44ZhQC3nYmvDlg==}
engines: {node: '>= 14.16'}
peerDependencies:
petite-vue-i18n: '*'
vue-i18n: '*'
vue-i18n-bridge: '*'
peerDependenciesMeta:
petite-vue-i18n:
optional: true
vue-i18n:
optional: true
vue-i18n-bridge:
optional: true
dependencies:
'@intlify/bundle-utils': 6.0.1(vue-i18n@9.2.2)
'@intlify/shared': 9.3.0-beta.17
'@rollup/pluginutils': 5.0.2(rollup@2.79.1)
'@vue/compiler-sfc': 3.2.47
debug: 4.3.4
fast-glob: 3.2.12
js-yaml: 4.1.0
json5: 2.2.3
pathe: 1.1.0
picocolors: 1.0.0
source-map: 0.6.1
unplugin: 1.3.1
vue-i18n: 9.2.2(vue@3.2.47)
transitivePeerDependencies:
- rollup
- supports-color
dev: true
/@intlify/vue-devtools@9.2.2:
resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==}
engines: {node: '>= 14'}
dependencies:
'@intlify/core-base': 9.2.2
'@intlify/shared': 9.2.2
/@istanbuljs/schema@0.1.3:
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
engines: {node: '>=8'}
@ -3032,7 +3146,6 @@ packages:
/@vue/devtools-api@6.5.0:
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
dev: false
/@vue/reactivity-transform@3.2.47:
resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
@ -3182,6 +3295,14 @@ packages:
acorn-walk: 7.2.0
dev: true
/acorn-jsx@5.3.2(acorn@7.4.1):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: 7.4.1
dev: true
/acorn-jsx@5.3.2(acorn@8.8.2):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -4777,6 +4898,18 @@ packages:
estraverse: 5.3.0
dev: true
/eslint-utils@2.1.0:
resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
engines: {node: '>=6'}
dependencies:
eslint-visitor-keys: 1.3.0
dev: true
/eslint-visitor-keys@1.3.0:
resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
engines: {node: '>=4'}
dev: true
/eslint-visitor-keys@3.4.0:
resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -4831,6 +4964,15 @@ packages:
- supports-color
dev: true
/espree@6.2.1:
resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
engines: {node: '>=6.0.0'}
dependencies:
acorn: 7.4.1
acorn-jsx: 5.3.2(acorn@7.4.1)
eslint-visitor-keys: 1.3.0
dev: true
/espree@9.5.1:
resolution: {integrity: sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -5951,6 +6093,17 @@ packages:
engines: {node: '>=6'}
hasBin: true
/jsonc-eslint-parser@1.4.1:
resolution: {integrity: sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==}
engines: {node: '>=8.10.0'}
dependencies:
acorn: 7.4.1
eslint-utils: 2.1.0
eslint-visitor-keys: 1.3.0
espree: 6.2.1
semver: 6.3.0
dev: true
/jsonc-eslint-parser@2.3.0:
resolution: {integrity: sha512-9xZPKVYp9DxnM3sd1yAsh/d59iIaswDkai8oTxbursfKYbg/ibjX0IzFt35+VZ8iEW453TVTXztnRvYUQlAfUQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -8498,6 +8651,18 @@ packages:
- supports-color
dev: true
/vue-i18n@9.2.2(vue@3.2.47):
resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
engines: {node: '>= 14'}
peerDependencies:
vue: ^3.0.0
dependencies:
'@intlify/core-base': 9.2.2
'@intlify/shared': 9.2.2
'@intlify/vue-devtools': 9.2.2
'@vue/devtools-api': 6.5.0
vue: 3.2.47
/vue-router@4.1.6(vue@3.2.47):
resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==}
peerDependencies:
@ -8928,6 +9093,14 @@ packages:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true
/yaml-eslint-parser@0.3.2:
resolution: {integrity: sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==}
dependencies:
eslint-visitor-keys: 1.3.0
lodash: 4.17.21
yaml: 1.10.2
dev: true
/yaml-eslint-parser@1.2.2:
resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==}
engines: {node: ^14.17.0 || >=16.0.0}
@ -8937,6 +9110,11 @@ packages:
yaml: 2.2.1
dev: true
/yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
dev: true
/yaml@2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}

View File

@ -11,6 +11,7 @@ import { naive } from './plugins/naive.plugin';
import App from './App.vue';
import router from './router';
import { i18nPlugin } from './plugins/i18n.plugin';
registerSW();
@ -18,6 +19,7 @@ const app = createApp(App);
app.use(createPinia());
app.use(createHead());
app.use(i18nPlugin);
app.use(router);
app.use(naive);
app.use(plausible);

View File

@ -9,6 +9,7 @@ import { config } from '@/config';
const toolStore = useToolStore();
useHead({ title: 'IT Tools - Handy online tools for developers' });
const { t } = useI18n();
</script>
<template>
@ -48,7 +49,7 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
</transition>
<div v-if="toolStore.newTools.length > 0">
<n-h3>Newest tools</n-h3>
<n-h3>{{ t('home.categories.newestTools') }}</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.newTools" :key="tool.name">
<ToolCard :tool="tool" />

View File

@ -0,0 +1,50 @@
import type { App } from 'vue';
import { createI18n } from 'vue-i18n';
import type { Locale } from 'vue-i18n';
const i18n = createI18n({
legacy: false,
locale: '',
messages: {},
});
const localesMap = Object.fromEntries(
Object.entries(import.meta.glob('../../locales/*.yml'))
.map(([path, loadLocale]) => [path.match(/([\w-]*)\.yml$/)?.[1], loadLocale]),
) as Record<Locale, () => Promise<{ default: Record<string, string> }>>;
export const availableLocales = Object.keys(localesMap);
const loadedLanguages: string[] = [];
function setI18nLanguage(lang: Locale) {
i18n.global.locale.value = lang as any;
if (typeof document !== 'undefined') {
document.querySelector('html')?.setAttribute('lang', lang);
}
return lang;
}
export async function loadLanguageAsync(lang: string): Promise<Locale> {
if (i18n.global.locale.value === lang) {
return setI18nLanguage(lang);
}
if (loadedLanguages.includes(lang)) {
return setI18nLanguage(lang);
}
const messages = await localesMap[lang]();
i18n.global.setLocaleMessage(lang, messages.default);
loadedLanguages.push(lang);
return setI18nLanguage(lang);
}
export const i18nPlugin = {
install: (app: App) => {
app.use(i18n);
loadLanguageAsync('en');
},
};

View File

@ -9,6 +9,6 @@
"paths": {
"@/*": ["./src/*"]
},
"types": ["naive-ui/volar", "unplugin-icons/types/vue"]
"types": ["naive-ui/volar", "unplugin-icons/types/vue", "@intlify/unplugin-vue-i18n/messages"]
}
}

View File

@ -1,4 +1,5 @@
import { fileURLToPath, URL } from 'url';
import { URL, fileURLToPath } from 'node:url';
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
@ -13,15 +14,23 @@ import Unocss from 'unocss/vite';
import { configDefaults } from 'vitest/config';
import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver';
import VueI18n from '@intlify/unplugin-vue-i18n/vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
VueI18n({
runtimeOnly: true,
compositionOnly: true,
fullInstall: true,
include: [resolve(__dirname, 'locales/**'), resolve(__dirname, 'src/tools/*/locales/**')],
}),
AutoImport({
imports: [
'vue',
'vue-router',
'@vueuse/core',
'vue-i18n',
{
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
},