Skip to content

Commit

Permalink
feat: add i18n support (#2)
Browse files Browse the repository at this point in the history
* feat: add i18n support

* chore: typo in favourite i18n message

* chore: remove async

* chore: refactor i18n conf and plugin

* chore: remove `vueI18n` from nuxt i18n entry

* chore: include single and multiple locale files

* chore: single es-* json files with wrong content

* chore(docs): add i18n support section

* chore(docs): add emoji to i18n support section

* chore: update `createProvideFunction`

* chore: cleanup
  • Loading branch information
userquin authored Jul 2, 2023
1 parent d0e3bfd commit ce1bbfc
Show file tree
Hide file tree
Showing 27 changed files with 790 additions and 31 deletions.
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
dist
node_modules
templates
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
shamefully-hoist=true
shamefully-hoist=true
shell-emulator=true
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Zero-config Nuxt module for Vuetify
- 🛠️ **Versatile**: custom Vuetify [directives](https://vuetifyjs.com/en/getting-started/installation/#manual-steps) and [labs components](https://vuetifyjs.com/en/labs/introduction/) registration
-**Configurable styles**: configure your variables using [Vuetify SASS Variables](https://vuetifyjs.com/en/features/sass-variables/)
- 💥 **SSR**: automatic SSR detection and configuration
- 🌍 **I18n ready**: install [@nuxtjs/i18n](https://v8.i18n.nuxtjs.org/) Nuxt module, and you're ready to use Vuetify [internationalization](https://vuetifyjs.com/en/features/internationalization/) features
- 🦾 **Type Strong**: written in [TypeScript](https://www.typescriptlang.org/)

## 📦 Install
Expand Down Expand Up @@ -73,6 +74,18 @@ export default defineNuxtConfig({
})
```

## 🌍 I18n support

> Requires latest [@nuxtjs/i18n](https://v8.i18n.nuxtjs.org/) Nuxt module: `8.0.0.beta.12`.
There is a [bug](https://github.com/nuxt-modules/i18n/pull/2193) in the current version that prevents `@nuxtjs/i18n` module to work properly when using `lazy` i18n files.

If you're using `lazy` i18n files per locale, apply [this patch](./patches/@nuxtjs__i18n@8.0.0-beta.12.patch) to your project: check how to apply it when using `pnpm` in the root `package.json` file in this repo: [./package.json#L25-L26](./package.json#L97-L101).

You can check the playground folder, you can run it using single or multiple json files per locale:
- for single file per locale: run from root folder `pnpm install && nr dev:prepare && nr dev`
- for multiple files per locale: run from root folder `pnpm install && nr dev:prepare:multiple-json && nr dev:multiple-json`

<!--
Read the [📖 documentation](https://vite-pwa-org.netlify.app/frameworks/nuxt) for a complete guide on how to configure and use
this plugin.
Expand Down
19 changes: 14 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@
"*.d.ts"
],
"scripts": {
"prepack": "nuxt-module-build",
"build": "pnpm dev:prepare && nuxt-module-build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:multiple-json": "MULTIPLE_LANG_FILES=true nuxi dev playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"prepublishOnly": "npm run prepack",
"release": "bumpp && npm publish",
"dev:prepare:multiple-json": "nuxt-module-build --stub && MULTIPLE_LANG_FILES=true nuxi prepare playground",
"dev:build": "nuxi build playground",
"dev:build:multiple-json": "MULTIPLE_LANG_FILES=true nuxi build playground",
"lint": "eslint .",
"lint:fix": "nr lint --fix",
"test": "vitest run",
"test:watch": "vitest watch"
"test:watch": "vitest watch",
"prepublishOnly": "pnpm build",
"release": "bumpp && npm publish"
},
"peerDependencies": {
"@nuxt/kit": "^3.5.3",
Expand All @@ -65,6 +68,7 @@
"@nuxt/module-builder": "^0.4.0",
"@nuxt/schema": "^3.6.1",
"@nuxt/test-utils": "^3.6.1",
"@nuxtjs/i18n": "^8.0.0-beta.12",
"@parcel/watcher": "^2.1.0",
"@types/node": "^18",
"bumpp": "^9.1.1",
Expand All @@ -90,5 +94,10 @@
"vite-plugin-vuetify",
"vuetify"
]
},
"pnpm": {
"patchedDependencies": {
"@nuxtjs/i18n@8.0.0-beta.12": "patches/@nuxtjs__i18n@8.0.0-beta.12.patch"
}
}
}
13 changes: 13 additions & 0 deletions patches/@nuxtjs__i18n@8.0.0-beta.12.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/dist/runtime/utils.mjs b/dist/runtime/utils.mjs
index 21f7780ac104abddfd5c8038e1574bc0e3212cf6..b2e7584912c9eede4d152f0ddaeb70ac917ec828 100644
--- a/dist/runtime/utils.mjs
+++ b/dist/runtime/utils.mjs
@@ -77,7 +77,7 @@ export async function loadInitialMessages(context, messages, options) {
const fallbackLocales = makeFallbackLocaleCodes(fallbackLocale, [defaultLocale, initialLocale]);
await Promise.all(fallbackLocales.map((locale) => loadLocale(context, locale, setter)));
}
- const locales = lazy ? [...(/* @__PURE__ */ new Set()).add(defaultLocale).add(initialLocale)] : localeCodes;
+ const locales = lazy ? localeCodes : [...(/* @__PURE__ */ new Set()).add(defaultLocale).add(initialLocale)];
await Promise.all(locales.map((locale) => loadLocale(context, locale, setter)));
}
return messages;
12 changes: 12 additions & 0 deletions playground/config/i18n.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { availableLocales } from './i18n'

export default defineI18nConfig(() => {
return {
legacy: false,
availableLocales: availableLocales.map(l => l.code),
fallbackLocale: 'en-US',
fallbackWarn: true,
// eslint-disable-next-line @typescript-eslint/comma-dangle
missingWarn: true
}
})
89 changes: 89 additions & 0 deletions playground/config/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { LocaleObject } from '#i18n'

const multipleJson = process.env.MULTIPLE_LANG_FILES === 'true'

const countryLocaleVariants: Record<string, LocaleObject[]> = {
en: [
// en.json contains en-US translations
{ code: 'en-US', name: 'English (US)' },
{ code: 'en-GB', name: 'English (UK)' },
],
es: [
// es.json contains es-ES translations
// { code: 'es-AR', name: 'Español (Argentina)' },
// { code: 'es-BO', name: 'Español (Bolivia)' },
// { code: 'es-CL', name: 'Español (Chile)' },
// { code: 'es-CO', name: 'Español (Colombia)' },
// { code: 'es-CR', name: 'Español (Costa Rica)' },
// { code: 'es-DO', name: 'Español (República Dominicana)' },
// { code: 'es-EC', name: 'Español (Ecuador)' },
{ code: 'es-ES', name: 'Español (España)' },
// TODO: Support es-419, if we include spanish country variants remove also fix on utils/language.ts module
{ code: 'es-419', name: 'Español (Latinoamérica)' },
// { code: 'es-GT', name: 'Español (Guatemala)' },
// { code: 'es-HN', name: 'Español (Honduras)' },
// { code: 'es-MX', name: 'Español (México)' },
// { code: 'es-NI', name: 'Español (Nicaragua)' },
// { code: 'es-PA', name: 'Español (Panamá)' },
// { code: 'es-PE', name: 'Español (Perú)' },
// { code: 'es-PR', name: 'Español (Puerto Rico)' },
// { code: 'es-SV', name: 'Español (El Salvador)' },
// { code: 'es-US', name: 'Español (Estados Unidos)' },
// { code: 'es-UY', name: 'Español (Uruguay)' },
// { code: 'es-VE', name: 'Español (Venezuela)' },
],
}

const locales: LocaleObject[] = [
{
code: 'en',
file: 'en.json',
name: 'English',
},
{
code: 'es',
file: 'es.json',
name: 'Español',
},
]

function buildLocales() {
const useLocales = Object.values(locales).reduce((acc, data) => {
const locales = countryLocaleVariants[data.code]
if (locales) {
locales.forEach((l) => {
let entry: LocaleObject
if (multipleJson) {
entry = {
...data,
code: l.code,
name: l.name,
files: [data.file!, `${l.code}.json`],
}
delete entry.file
}
else {
entry = {
...data,
code: l.code,
name: l.name,
file: `${l.code}.json`,
}
}

acc.push(entry)
})
}
else {
acc.push(data)
}

return acc
}, <LocaleObject[]>[])

return useLocales.sort((a, b) => a.code.localeCompare(b.code))
}

export const availableLocales = buildLocales()

export const langDir = multipleJson ? 'locales/multiple' : 'locales/single'
16 changes: 8 additions & 8 deletions playground/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
</script>

<template>
<VContainer>
<VRow>
<VCol>
<VSheet
<v-container>
<v-row>
<v-col>
<v-sheet
class="pa-4 d-flex align-center flex-column"
color="grey-lighten-3"
rounded="lg"
>
<NuxtPage />
</VSheet>
</VCol>
</VRow>
</VContainer>
</v-sheet>
</v-col>
</v-row>
</v-container>
</template>
3 changes: 3 additions & 0 deletions playground/locales/multiple/en-GB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favourite"
}
3 changes: 3 additions & 0 deletions playground/locales/multiple/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorite"
}
3 changes: 3 additions & 0 deletions playground/locales/multiple/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorite"
}
1 change: 1 addition & 0 deletions playground/locales/multiple/es-419.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/multiple/es-ES.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions playground/locales/multiple/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorito"
}
3 changes: 3 additions & 0 deletions playground/locales/single/en-GB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favourite"
}
3 changes: 3 additions & 0 deletions playground/locales/single/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorite"
}
3 changes: 3 additions & 0 deletions playground/locales/single/es-419.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorito (es-419)"
}
3 changes: 3 additions & 0 deletions playground/locales/single/es-ES.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"xxx": "Favorito (es-ES)"
}
19 changes: 18 additions & 1 deletion playground/nuxt.config.ts → playground/nuxt.config.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { md3 } from 'vuetify/blueprints'
import { availableLocales, langDir } from './config/i18n'

export default defineNuxtConfig({
ssr: true,
Expand All @@ -10,7 +11,23 @@ export default defineNuxtConfig({
},
},
},
modules: ['../src/module'],
imports: {
autoImport: true,
injectAtEnd: true,
},
modules: ['@nuxtjs/i18n', '../src/module'],
i18n: {
locales: availableLocales,
lazy: true,
strategy: 'no_prefix',
detectBrowserLanguage: false,
langDir,
defaultLocale: 'en-US',
customRoutes: undefined,
dynamicRouteParams: false,
// debug: true,
vueI18n: './config/i18n.config.mts',
},
vuetify: {
moduleOptions: {
styles: { configFile: '/settings.scss' },
Expand Down
3 changes: 3 additions & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"build": "nuxi build",
"generate": "nuxi generate"
},
"dependencies": {
"@nuxtjs/i18n": "^8.0.0-beta.12"
},
"devDependencies": {
"nuxt": "latest",
"sass": "^1.63.6"
Expand Down
31 changes: 28 additions & 3 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { useLocale } from 'vuetify'
const value = reactive<{
name1?: string
name2?: string
Expand All @@ -8,17 +10,40 @@ const value = reactive<{
name2: undefined,
name3: undefined,
})
const { locales, t } = useI18n()
const { current } = useLocale()
watch(current, () => {
console.log('current', t('xxx', { locale: current.value }))
})
</script>

<template>
<div>
<VTextField
<div>Vuetify useLocale(): {{ current }}</div>
<div>$i18n current: {{ $i18n.locale }}</div>
<div>$vuetify.locale.current: {{ $vuetify.locale.current }}</div>
<div>t without locale: {{ t('xxx') }}</div>
<div>t with I18N locale: {{ t('xxx', { locale: $i18n.locale }) }}</div>
<div>t with Vuetify current locale: {{ t('xxx', { locale: current }) }}</div>
<div>$t {{ $t('xxx') }}</div>
<div>$vuetify.locale.t {{ $vuetify.locale.t('xxx') }}</div>
<v-select
v-model="current"
:items="locales"
item-title="name"
item-value="code"
outlined
/>
<v-text-field
v-model="value.name1"
label="Name 1"
:label="t('xxx')"
hint="name 1"
persistent-hint
outlined
/>
<VBtn>ja</VBtn>
<v-btn>{{ t('xxx') }}</v-btn>
<v-locale-provider locale="es-ES">
<v-btn>{{ $vuetify.locale.t('xxx') }}</v-btn>
</v-locale-provider>
</div>
</template>
Loading

0 comments on commit ce1bbfc

Please sign in to comment.