@futurra/multilinguist 0.0.0-stage → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -2
- package/dist/module.d.mts +5 -0
- package/dist/module.json +18 -0
- package/dist/module.mjs +125 -0
- package/dist/runtime/composables/useLocale.d.ts +12 -0
- package/dist/runtime/composables/useLocale.js +30 -0
- package/dist/runtime/composables/useLocalization.d.ts +13 -0
- package/dist/runtime/composables/useLocalization.js +76 -0
- package/dist/runtime/composables/useMultilinguist.d.ts +8 -0
- package/dist/runtime/composables/useMultilinguist.js +9 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +25 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/types/generated-locales.d.ts +40 -0
- package/dist/runtime/types/locale-imports.d.ts +30 -0
- package/dist/runtime/types/locale-imports.js +6 -0
- package/dist/types.d.mts +7 -0
- package/package.json +77 -4
package/README.md
CHANGED
|
@@ -1,3 +1,234 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nuxt Multilinguist module
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multilinguist is a simple but smoothly working module for easy and seamless localization implementation for Nuxt
|
|
4
|
+
applications.
|
|
5
|
+
|
|
6
|
+
## Key Features
|
|
7
|
+
|
|
8
|
+
- Easy translations that you're used to
|
|
9
|
+
- Works perfectly fine on both SSR + CSR
|
|
10
|
+
- No memory leaks and running out of memory errors
|
|
11
|
+
- Autocompletion & validation of keys
|
|
12
|
+
- Autodetection of browser's locale
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @futurra/multilinguist
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then, add the module to your nuxt.config
|
|
21
|
+
|
|
22
|
+
```nuxt.config.ts
|
|
23
|
+
export default defineNuxtConfig({
|
|
24
|
+
modules: [
|
|
25
|
+
"@futurra/multilinguist",
|
|
26
|
+
],
|
|
27
|
+
multilinguist: {
|
|
28
|
+
defaultLocale: "en", // string representing key to your default (fallback) locale
|
|
29
|
+
supportedLanguages: ["en", "es"], // array of strings representing all available locales' keys
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then, create a "locales" directory in root directory of your project.
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
Now, you're ready to use Multilinguist Module!
|
|
39
|
+
|
|
40
|
+
After running your project, you can see the following warning:
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
**Dont be scared**, this is just a message from the module, indicating that your locales are typed, and that the module
|
|
45
|
+
can properly perform type-checks and autocompletion.
|
|
46
|
+
|
|
47
|
+
Also, on both SSR and CSR, you will see the following message:
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
It indicated that the module has been initialized successfully.
|
|
52
|
+
|
|
53
|
+
To disable logs, you can set the `logging` option in your nuxt.config to `false` (by default, it is `true`):
|
|
54
|
+
|
|
55
|
+
```nuxt.config.ts
|
|
56
|
+
export default defineNuxtConfig({
|
|
57
|
+
modules: [
|
|
58
|
+
"@futurra/multilinguist",
|
|
59
|
+
],
|
|
60
|
+
multilinguist: {
|
|
61
|
+
defaultLocale: "en",
|
|
62
|
+
supportedLanguages: ["en", "es"],
|
|
63
|
+
logging: false, // Boolean value to define if the module should send internal logs to console
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
Also, Multilinguist offers a functionality to set the default browser's locale as default if it is available in the supported languages array.
|
|
68
|
+
|
|
69
|
+
To disable this option, you can set the `setBrowserLanguage` option to `false` as it is `true` by default:
|
|
70
|
+
|
|
71
|
+
```nuxt.config.ts
|
|
72
|
+
export default defineNuxtConfig({
|
|
73
|
+
modules: [
|
|
74
|
+
"@futurra/multilinguist",
|
|
75
|
+
],
|
|
76
|
+
multilinguist: {
|
|
77
|
+
defaultLocale: "en",
|
|
78
|
+
supportedLanguages: ["en", "es"],
|
|
79
|
+
logging: false,
|
|
80
|
+
setBrowserLanguage: false, // by default: true
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Also, if you want to use a different folder name for your locales' files, or you want to specify other path to the directory, you can set the `localesPath` option in your nuxt.config:
|
|
86
|
+
|
|
87
|
+
```nuxt.config.ts
|
|
88
|
+
export default defineNuxtConfig({
|
|
89
|
+
modules: [
|
|
90
|
+
"@futurra/multilinguist",
|
|
91
|
+
],
|
|
92
|
+
multilinguist: {
|
|
93
|
+
defaultLocale: "en",
|
|
94
|
+
supportedLanguages: ["en", "es"],
|
|
95
|
+
logging: false,
|
|
96
|
+
setBrowserLanguage: false, // by default: true
|
|
97
|
+
localesPath: "./languages", // by default: "./locales"
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Important! Keep in mind, that it is not recommended to use "public" folder for your locales' files, as it may lead to rendering, security and other issues.
|
|
103
|
+
|
|
104
|
+
# Usage
|
|
105
|
+
|
|
106
|
+
### t()—famous translate function
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
|
|
110
|
+
<script setup lang="ts">
|
|
111
|
+
const { t } = useMultilinguist(); // Call useMultilinguist composable to get the translation function
|
|
112
|
+
|
|
113
|
+
const pageTitle = computed(() => {
|
|
114
|
+
return t("Hello, World");
|
|
115
|
+
});
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<h3>{{ pageTitle }}</h3>
|
|
120
|
+
<button>{{ t("Switch Locale") }}</button>
|
|
121
|
+
</template>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It also supports nested keys and dynamic keys with variables;
|
|
125
|
+
you only need to pass the second argument, an object with used in the key variables:
|
|
126
|
+
|
|
127
|
+
```vue
|
|
128
|
+
|
|
129
|
+
<template>
|
|
130
|
+
<span>{{ t("Paste your variable here", { variable: locale }) }}</span>
|
|
131
|
+
</template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
And your JSON must look like that:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"Paste your variable here": "Here is your variable: {variable}"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
|
|
144
|
+
<script setup lang="ts">
|
|
145
|
+
const { t } = useMultilinguist(); // Call useMultilinguist composable to get the translation function
|
|
146
|
+
|
|
147
|
+
const pageTitle = computed(() => {
|
|
148
|
+
return t("Hello, World");
|
|
149
|
+
});
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<template>
|
|
153
|
+
<h3>{{ pageTitle }}</h3>
|
|
154
|
+
<button>{{ t("Switch Locale") }}</button>
|
|
155
|
+
<br />
|
|
156
|
+
<span>{{ t("Paste your variable here", { variable: locale }) }}</span>
|
|
157
|
+
<br />
|
|
158
|
+
<h1>{{ t("nested.Nested key") }}: <span>{{ t("nested.Language") }}</span></h1>
|
|
159
|
+
</template>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
You can already see how keys auto-completion works:
|
|
163
|
+
|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
And validation:
|
|
167
|
+
|
|
168
|
+

|
|
169
|
+
|
|
170
|
+
### Set another value to the current locale:
|
|
171
|
+
|
|
172
|
+
```vue
|
|
173
|
+
|
|
174
|
+
<script setup lang="ts">
|
|
175
|
+
const { t, setLocale } = useMultilinguist();
|
|
176
|
+
// setLocale function accepts a string that should match one of defined
|
|
177
|
+
// in the nuxt.config strings from supportedLanguages array
|
|
178
|
+
|
|
179
|
+
const pageTitle = computed(() => {
|
|
180
|
+
return t("Hello, World");
|
|
181
|
+
});
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<template>
|
|
185
|
+
<h3>{{ pageTitle }}</h3>
|
|
186
|
+
<button @click="setLocale('es')">{{ t("Switch Locale") }}</button>
|
|
187
|
+
</template>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Get current locale
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
const { t, setLocale, locale } = useMultilinguist();
|
|
196
|
+
// locale is SSR friendly, shared among the app ref state
|
|
197
|
+
|
|
198
|
+
const pageTitle = computed(() => {
|
|
199
|
+
return t("Hello, World");
|
|
200
|
+
});
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<template>
|
|
204
|
+
<h3>{{ pageTitle }}</h3>
|
|
205
|
+
<h6>Current Locale: {{ locale }}</h6>
|
|
206
|
+
<button @click="setLocale('es')">{{ t("Switch Locale") }}</button>
|
|
207
|
+
</template>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Get locales list
|
|
211
|
+
|
|
212
|
+
```vue
|
|
213
|
+
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
const { t, setLocale, locale, locales } = useMultilinguist();
|
|
216
|
+
// Locales is a computed ref, which value is being set from your nuxt.config
|
|
217
|
+
|
|
218
|
+
const pageTitle = computed(() => {
|
|
219
|
+
return t("Hello, World");
|
|
220
|
+
});
|
|
221
|
+
</script>
|
|
222
|
+
|
|
223
|
+
<template>
|
|
224
|
+
<h3>{{ pageTitle }}</h3>
|
|
225
|
+
<h6>Current Locale: {{ locale }}</h6>
|
|
226
|
+
<span
|
|
227
|
+
v-for="localeItem from locales"
|
|
228
|
+
:key="localeItem"
|
|
229
|
+
>
|
|
230
|
+
| {{ localeItem }} |
|
|
231
|
+
</span>
|
|
232
|
+
<button @click="setLocale('es')">{{ t("Switch Locale") }}</button>
|
|
233
|
+
</template>
|
|
234
|
+
```
|
package/dist/module.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@futurra/multilinguist",
|
|
3
|
+
"configKey": "multilinguist",
|
|
4
|
+
"version": "1.6.2",
|
|
5
|
+
"compatibility": {
|
|
6
|
+
"nuxt": "^3.0.0 || ^4.0.0"
|
|
7
|
+
},
|
|
8
|
+
"defaults": {
|
|
9
|
+
"logging": true,
|
|
10
|
+
"defaultLocale": "",
|
|
11
|
+
"supportedLanguages": [],
|
|
12
|
+
"setBrowserLanguage": true
|
|
13
|
+
},
|
|
14
|
+
"builder": {
|
|
15
|
+
"@nuxt/module-builder": "1.0.1",
|
|
16
|
+
"unbuild": "3.6.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addPlugin, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function flattenKeys(obj, prefix = "") {
|
|
6
|
+
return Object.entries(obj).flatMap(([key, value]) => {
|
|
7
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
8
|
+
if (typeof value === "object" && value !== null) {
|
|
9
|
+
return flattenKeys(value, fullKey);
|
|
10
|
+
}
|
|
11
|
+
return [fullKey];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const generateOutputString = (func) => {
|
|
15
|
+
return `
|
|
16
|
+
// AUTO-GENERATED FILE \u2014 DO NOT EDIT MANUALLY
|
|
17
|
+
|
|
18
|
+
export interface TranslationMessages {
|
|
19
|
+
${func()}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LocaleKey = keyof TranslationMessages;
|
|
23
|
+
|
|
24
|
+
export type TFunction = <K extends LocaleKey>(
|
|
25
|
+
key: K,
|
|
26
|
+
variables?: Record<string, string>
|
|
27
|
+
) => string;
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
type LocaleKey = keyof TranslationMessages;
|
|
31
|
+
type TFunction = <K extends LocaleKey>(
|
|
32
|
+
key: K,
|
|
33
|
+
variables?: Record<string, string>
|
|
34
|
+
) => string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare module '#app' {
|
|
38
|
+
interface NuxtApp {
|
|
39
|
+
$t: TFunction;
|
|
40
|
+
t: TFunction;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare module 'vue' {
|
|
45
|
+
interface ComponentCustomProperties {
|
|
46
|
+
$t: TFunction;
|
|
47
|
+
t: TFunction;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
};
|
|
52
|
+
function GenerateLocaleKeysPlugin(defaultLocaleFromConfig, absoluteLocalesPath, outPath, logging = true) {
|
|
53
|
+
async function generateTypes() {
|
|
54
|
+
const defaultLocalePath = path.join(absoluteLocalesPath, `${defaultLocaleFromConfig}.json`);
|
|
55
|
+
if (!fs.existsSync(defaultLocalePath)) {
|
|
56
|
+
console?.error(`\u274C Default locale file not found: ${defaultLocalePath}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const json = JSON.parse(fs.readFileSync(defaultLocalePath, "utf-8"));
|
|
60
|
+
const keys = flattenKeys(json);
|
|
61
|
+
const output = generateOutputString(() => keys.map((key) => ` ${JSON.stringify(key)}: string;`).join("\n"));
|
|
62
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
63
|
+
fs.writeFileSync(outPath, output, "utf-8");
|
|
64
|
+
if (logging) {
|
|
65
|
+
console?.warn(`\u2705 Generated types to ${outPath} from ${defaultLocalePath}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: "vite-plugin-generate-locales-types",
|
|
70
|
+
async buildStart() {
|
|
71
|
+
await generateTypes();
|
|
72
|
+
},
|
|
73
|
+
async handleHotUpdate(ctx) {
|
|
74
|
+
if (ctx.file.endsWith(".json") && ctx.file.includes(absoluteLocalesPath)) {
|
|
75
|
+
await generateTypes();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const module = defineNuxtModule({
|
|
82
|
+
meta: {
|
|
83
|
+
name: "@futurra/multilinguist",
|
|
84
|
+
configKey: "multilinguist",
|
|
85
|
+
version: "1.6.2",
|
|
86
|
+
compatibility: {
|
|
87
|
+
nuxt: "^3.0.0 || ^4.0.0"
|
|
88
|
+
},
|
|
89
|
+
defaults: {
|
|
90
|
+
logging: true,
|
|
91
|
+
defaultLocale: "",
|
|
92
|
+
supportedLanguages: [],
|
|
93
|
+
setBrowserLanguage: true
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
setup(moduleOptions, nuxtApp) {
|
|
97
|
+
const resolver = createResolver(import.meta.url);
|
|
98
|
+
addPlugin(resolver.resolve("runtime/plugin"));
|
|
99
|
+
addImportsDir(resolver.resolve("runtime/composables"));
|
|
100
|
+
nuxtApp.options.runtimeConfig.public.multilinguist = {
|
|
101
|
+
defaultLocale: moduleOptions.defaultLocale,
|
|
102
|
+
supportedLanguages: moduleOptions.supportedLanguages,
|
|
103
|
+
logging: typeof moduleOptions.logging === "boolean" ? moduleOptions.logging : true,
|
|
104
|
+
setBrowserLanguage: typeof moduleOptions.setBrowserLanguage === "boolean" ? moduleOptions.setBrowserLanguage : true
|
|
105
|
+
};
|
|
106
|
+
nuxtApp.hook("vite:extendConfig", (viteConfig) => {
|
|
107
|
+
viteConfig.plugins = viteConfig.plugins || [];
|
|
108
|
+
viteConfig.plugins.push(
|
|
109
|
+
GenerateLocaleKeysPlugin(
|
|
110
|
+
moduleOptions.defaultLocale,
|
|
111
|
+
`${nuxtApp.options.rootDir}/public/locales`,
|
|
112
|
+
resolver.resolve("./runtime/types/generated-locales.d.ts"),
|
|
113
|
+
moduleOptions.logging
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
nuxtApp.hook("prepare:types", ({ references }) => {
|
|
118
|
+
references.push({
|
|
119
|
+
path: resolver.resolve("./runtime/types/generated-locales.d.ts")
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export { module as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Locale, TranslationMap } from "./useLocalization.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a browser locale code (e.g. "pt-BR", "en-US", "de") against the list of supported locales.
|
|
4
|
+
*
|
|
5
|
+
* 1. Exact match on the full code (case-insensitive): "pt-BR" -> "pt-BR"
|
|
6
|
+
* 2. Fallback to the base language part before the "-": "pt-BR" -> "pt"
|
|
7
|
+
* 3. Otherwise the default locale.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveLocale<const T extends TranslationMap>(candidate: string | undefined | null, supportedLanguages: T, defaultLocale: Locale<T>): Locale<T>;
|
|
10
|
+
export default function useLocale<const T extends TranslationMap>(supportedLanguages: T, defaultLocale: Locale<T>): {
|
|
11
|
+
locale: import("vue").ComputedRef<Locale<T>>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useRequestHeaders } from "nuxt/app";
|
|
3
|
+
export function resolveLocale(candidate, supportedLanguages, defaultLocale) {
|
|
4
|
+
const normalized = candidate?.trim().toLowerCase();
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
return defaultLocale;
|
|
7
|
+
}
|
|
8
|
+
const exact = supportedLanguages.find((supported) => supported.toLowerCase() === normalized);
|
|
9
|
+
if (exact) {
|
|
10
|
+
return exact;
|
|
11
|
+
}
|
|
12
|
+
const base = normalized.split("-")[0];
|
|
13
|
+
const partial = supportedLanguages.find((supported) => supported.toLowerCase() === base);
|
|
14
|
+
return partial ?? defaultLocale;
|
|
15
|
+
}
|
|
16
|
+
export default function useLocale(supportedLanguages, defaultLocale) {
|
|
17
|
+
const locale = computed(() => {
|
|
18
|
+
if (import.meta.server) {
|
|
19
|
+
const headers = useRequestHeaders(["accept-language"]);
|
|
20
|
+
const browserLocale = headers["accept-language"]?.split(",")[0]?.split(";")[0];
|
|
21
|
+
return resolveLocale(browserLocale, supportedLanguages, defaultLocale);
|
|
22
|
+
} else if (import.meta.client) {
|
|
23
|
+
return resolveLocale(navigator.language, supportedLanguages, defaultLocale);
|
|
24
|
+
}
|
|
25
|
+
return defaultLocale;
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
locale
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ComputedRef, type Ref } from "vue";
|
|
2
|
+
import type { LocaleKey } from "../types/generated-locales.js";
|
|
3
|
+
export type TranslationMap = readonly string[];
|
|
4
|
+
export type Locale<T extends TranslationMap> = T[number];
|
|
5
|
+
export type LocaleKeys<T extends TranslationMap> = T[keyof T];
|
|
6
|
+
export type TMultilinguistResponse<T extends TranslationMap> = {
|
|
7
|
+
t<K extends LocaleKey>(key: K, variables?: Record<string, string>): string;
|
|
8
|
+
setLocale: (locale: Locale<T>) => void;
|
|
9
|
+
initLocalization: () => Promise<void>;
|
|
10
|
+
locale: Ref<Locale<T>>;
|
|
11
|
+
locales: ComputedRef<T>;
|
|
12
|
+
};
|
|
13
|
+
export default function useLocalization<const T extends TranslationMap>(supportedLanguages: T, defaultLocale: Locale<T>, setBrowserLanguage?: boolean): TMultilinguistResponse<T>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import useLocale from "../composables/useLocale.js";
|
|
2
|
+
import { useCookie, useState } from "nuxt/app";
|
|
3
|
+
import { computed, watch } from "vue";
|
|
4
|
+
export default function useLocalization(supportedLanguages, defaultLocale, setBrowserLanguage = true) {
|
|
5
|
+
const { locale: userBrowserLocale } = useLocale(supportedLanguages, defaultLocale);
|
|
6
|
+
const userSelectedLocale = useCookie("multilinguist-locale", {
|
|
7
|
+
default: () => supportedLanguages.includes(userBrowserLocale.value) && setBrowserLanguage ? userBrowserLocale.value : defaultLocale
|
|
8
|
+
});
|
|
9
|
+
const localeFiles = import.meta.glob("@@/public/locales/*.json", { eager: true });
|
|
10
|
+
const loadedLanguages = useState("loaded-languages", () => ({}));
|
|
11
|
+
const loadLocaleMessages = async (locale2) => {
|
|
12
|
+
if (!loadedLanguages.value[locale2]) {
|
|
13
|
+
const fileKeyVersion4 = `../public/locales/${locale2}.json`;
|
|
14
|
+
const fileKeyVersion3 = `/public/locales/${locale2}.json`;
|
|
15
|
+
const messages = localeFiles[fileKeyVersion4] || localeFiles[fileKeyVersion3];
|
|
16
|
+
if (messages) {
|
|
17
|
+
loadedLanguages.value[locale2] = messages?.default;
|
|
18
|
+
} else {
|
|
19
|
+
if (!localeFiles[fileKeyVersion4]) {
|
|
20
|
+
throw new Error(`Locale file ${localeFiles[fileKeyVersion4]} not found`);
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`Locale file ${localeFiles[fileKeyVersion3]} not found`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const userPrefferableLocale = computed(() => {
|
|
28
|
+
return userSelectedLocale.value ? userSelectedLocale.value : userBrowserLocale.value;
|
|
29
|
+
});
|
|
30
|
+
const locale = useState("current-locale-multilinguist", () => {
|
|
31
|
+
return userPrefferableLocale.value || defaultLocale;
|
|
32
|
+
});
|
|
33
|
+
const t = (key, variables) => {
|
|
34
|
+
const messages = loadedLanguages.value[locale.value];
|
|
35
|
+
let translatedText = messages?.[key] ?? key;
|
|
36
|
+
if (key.includes(".") && !messages?.[key]) {
|
|
37
|
+
const nestedKeyArray = key.split(".");
|
|
38
|
+
translatedText = nestedKeyArray.reduce((acc, key2) => acc?.[key2], messages);
|
|
39
|
+
}
|
|
40
|
+
if (variables && typeof translatedText === "string") {
|
|
41
|
+
translatedText = translatedText.replace(/\{([^}]+)\}/g, (_, varKey) => {
|
|
42
|
+
return variables[varKey] ?? `{${varKey}}`;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return String(translatedText);
|
|
46
|
+
};
|
|
47
|
+
const setLocale = async (newLocale) => {
|
|
48
|
+
if (!loadedLanguages.value[newLocale] && supportedLanguages.includes(newLocale)) {
|
|
49
|
+
await loadLocaleMessages(newLocale);
|
|
50
|
+
}
|
|
51
|
+
userSelectedLocale.value = newLocale;
|
|
52
|
+
locale.value = newLocale;
|
|
53
|
+
};
|
|
54
|
+
const initLocalization = async () => {
|
|
55
|
+
await loadLocaleMessages(defaultLocale);
|
|
56
|
+
await setLocale(userPrefferableLocale.value);
|
|
57
|
+
};
|
|
58
|
+
const locales = computed(() => {
|
|
59
|
+
return supportedLanguages;
|
|
60
|
+
});
|
|
61
|
+
watch(
|
|
62
|
+
() => locale.value,
|
|
63
|
+
async (val) => {
|
|
64
|
+
if (val !== userSelectedLocale.value) {
|
|
65
|
+
userSelectedLocale.value = val;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
t,
|
|
71
|
+
setLocale,
|
|
72
|
+
initLocalization,
|
|
73
|
+
locale,
|
|
74
|
+
locales
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TranslationMap } from "./useLocalization.js";
|
|
2
|
+
export default function useMultilinguist(): {
|
|
3
|
+
t<K extends import("../types/generated-locales.js").LocaleKey>(key: K, variables?: Record<string, string>): string;
|
|
4
|
+
setLocale: (locale: string) => void;
|
|
5
|
+
initLocalization: () => Promise<void>;
|
|
6
|
+
locale: import("vue").Ref<string, string>;
|
|
7
|
+
locales: import("vue").ComputedRef<TranslationMap>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useNuxtApp } from "nuxt/app";
|
|
2
|
+
export default function useMultilinguist() {
|
|
3
|
+
const nuxtApp = useNuxtApp();
|
|
4
|
+
const localization = nuxtApp.$localization || nuxtApp._localization;
|
|
5
|
+
if (!localization) {
|
|
6
|
+
throw new Error("Localization plugin not initialized");
|
|
7
|
+
}
|
|
8
|
+
return { ...localization };
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#app";
|
|
2
|
+
import useLocalization from "../runtime/composables/useLocalization";
|
|
3
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
4
|
+
const config = nuxtApp.$config.public.multilinguist || {
|
|
5
|
+
defaultLocale: "en",
|
|
6
|
+
supportedLanguages: ["en"],
|
|
7
|
+
logging: true,
|
|
8
|
+
setBrowserLanguage: true
|
|
9
|
+
};
|
|
10
|
+
const supportedLanguages = config.supportedLanguages;
|
|
11
|
+
const defaultLocale = config.defaultLocale;
|
|
12
|
+
const shouldSetBrowserLanguage = config.setBrowserLanguage;
|
|
13
|
+
const { initLocalization, ...localizationProperties } = useLocalization(
|
|
14
|
+
supportedLanguages,
|
|
15
|
+
defaultLocale,
|
|
16
|
+
shouldSetBrowserLanguage
|
|
17
|
+
);
|
|
18
|
+
await initLocalization();
|
|
19
|
+
if (nuxtApp.$config.public.multilinguist.logging) {
|
|
20
|
+
console.log("[multilinguist] is initialised");
|
|
21
|
+
}
|
|
22
|
+
nuxtApp.provide("localization", localizationProperties);
|
|
23
|
+
nuxtApp.provide("t", localizationProperties.t);
|
|
24
|
+
nuxtApp._localization = localizationProperties;
|
|
25
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
// AUTO-GENERATED FILE — DO NOT EDIT MANUALLY
|
|
3
|
+
|
|
4
|
+
export interface TranslationMessages {
|
|
5
|
+
"Hello, World": string;
|
|
6
|
+
"Switch Locale": string;
|
|
7
|
+
"Paste your variable here": string;
|
|
8
|
+
"nested.Nested key": string;
|
|
9
|
+
"nested.Language": string;
|
|
10
|
+
"nested.nested second level.nested second level language": string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type LocaleKey = keyof TranslationMessages;
|
|
14
|
+
|
|
15
|
+
export type TFunction = <K extends LocaleKey>(
|
|
16
|
+
key: K,
|
|
17
|
+
variables?: Record<string, string>
|
|
18
|
+
) => string;
|
|
19
|
+
|
|
20
|
+
declare global {
|
|
21
|
+
type LocaleKey = keyof TranslationMessages;
|
|
22
|
+
type TFunction = <K extends LocaleKey>(
|
|
23
|
+
key: K,
|
|
24
|
+
variables?: Record<string, string>
|
|
25
|
+
) => string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module '#app' {
|
|
29
|
+
interface NuxtApp {
|
|
30
|
+
$t: TFunction;
|
|
31
|
+
t: TFunction;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare module 'vue' {
|
|
36
|
+
interface ComponentCustomProperties {
|
|
37
|
+
$t: TFunction;
|
|
38
|
+
t: TFunction;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const localeFiles: {
|
|
2
|
+
"/locales/en.json": {
|
|
3
|
+
default: {
|
|
4
|
+
"Hello, World": string;
|
|
5
|
+
"Switch Locale": string;
|
|
6
|
+
"Paste your variable here": string;
|
|
7
|
+
nested: {
|
|
8
|
+
"Nested key": string;
|
|
9
|
+
Language: string;
|
|
10
|
+
"nested second level": {
|
|
11
|
+
"nested second level language": string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
"/locales/es.json": {
|
|
17
|
+
default: {
|
|
18
|
+
"Hello, World": string;
|
|
19
|
+
"Switch Locale": string;
|
|
20
|
+
"Paste your variable here": string;
|
|
21
|
+
nested: {
|
|
22
|
+
"Nested key": string;
|
|
23
|
+
Language: string;
|
|
24
|
+
"nested second level": {
|
|
25
|
+
"nested second level language": string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
package/dist/types.d.mts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futurra/multilinguist",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Nuxt Multilinguist module for localizations",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://gitlab.fx2.io/ai/frontend/multilinguist.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/module.mjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/module.mjs",
|
|
15
|
+
"types": "./dist/types.d.mts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"types": "./dist/types.d.mts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"runtime/types"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"localization",
|
|
25
|
+
"nuxt localization",
|
|
26
|
+
"nuxt",
|
|
27
|
+
"internationalization",
|
|
28
|
+
"languages",
|
|
29
|
+
"locales",
|
|
30
|
+
"translation",
|
|
31
|
+
"localization nuxt",
|
|
32
|
+
"translation nuxt",
|
|
33
|
+
"nuxt translation",
|
|
34
|
+
"nuxt multilinguist",
|
|
35
|
+
"multilinguist nuxt",
|
|
36
|
+
"ssr localization"
|
|
37
|
+
],
|
|
38
|
+
"bugs": {
|
|
39
|
+
"email": "michaelkukh.dev@gmail.com"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"prepack": "nuxt-module-build build",
|
|
43
|
+
"dev": "nuxi dev playground",
|
|
44
|
+
"dev:build": "nuxi build playground",
|
|
45
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
46
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest watch",
|
|
50
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
51
|
+
"build": "vite build"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"nuxt": "^3.0.0 || ^4.0.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"eslint-config-prettier": "^10.1.5",
|
|
58
|
+
"eslint-plugin-prettier": "^5.4.1",
|
|
59
|
+
"prettier": "^3.5.3"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@nuxt/devtools": "^2.4.1",
|
|
63
|
+
"@nuxt/eslint-config": "^1.4.1",
|
|
64
|
+
"@nuxt/kit": "^4.0.1",
|
|
65
|
+
"@nuxt/module-builder": "^1.0.1",
|
|
66
|
+
"@nuxt/schema": "^4.0.1",
|
|
67
|
+
"@nuxt/test-utils": "^3.19.1",
|
|
68
|
+
"@types/node": "latest",
|
|
69
|
+
"changelogen": "^0.6.1",
|
|
70
|
+
"eslint": "^9.27.0",
|
|
71
|
+
"nuxt": "^4.0.3",
|
|
72
|
+
"typescript": "~5.8.3",
|
|
73
|
+
"vitest": "^3.1.4",
|
|
74
|
+
"vue-tsc": "^2.2.10"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
78
|
+
}
|
|
79
|
+
}
|