bundle-tools
bundle-tools copied to clipboard
@intlify/vite-plugin-vue-i18n locale translation files splitting
Clear and concise description of the problem
- for some projects custom block code is not the right way
- if we want place locale files in separate directory, we need to use "global" access, so everything is in one file per locale, example (these files can really grow in size and be really unclear):
locales/en.yaml
locales/fr.yaml
- if we split locale translation files into multiple files, import all locales at once is not working, because its not correctly merged
Suggested solution
- add possibility to split each locale to multiple files, so our structure can look like this:
locales/en/group1/subject1.yaml
locales/en/group1/subject2.yaml
locales/en/group2/subject3.yaml
locales/fr/group1/subject1.yaml
locales/fr/group1/subject2.yaml
locales/fr/group2/subject3.yaml
There are to possibilities how to achieve this:
- more complex way - first directory will define the locale and each directory is a subject path, so if
locales/en/group1/subject1.yamlcontent is this:
foo: foo
bar:
baz: baz
the "generated" file for en locale will be:
group1:
subject1:
foo: foo
bar:
baz: baz
- more simple way - just merge the content, use root subject as locale - we will need to define whole subject in each file, the plugin will just merge everything in one
locales/en/group1/subject1.yaml
en:
group1:
subject1:
foo: foo
bar:
baz: baz
locales/fr/group1/subject1.yaml
fr:
group1:
subject1:
foo: foo
bar:
baz: baz
Alternative
If there is any existing way how to achieve similar code spliting right now without need to implement it, please add it to documentation, because now there is nothing about it.
Additional context
I tried several ways how to structure the dirs, files and content of yaml/json files and nothing was correctly merged, always only one file worked and other were ignored.
Validations
- [X] Read the Contributing Guidelines.
- [X] Read the README
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
Would love to have support for this! Currently, I'm having to cram all my i18n strings into one JSON file to make it work, and that route is really not scalable.
i made for now my custom load for this. for now without dynamic import (all languages and all files are imported) vite is a requirement for this (glob import and load of yaml, but probably can be replaced by some library for webpack):
// @/locales/index.js
// note: now import is not dynamic, so all language files are loaded at once
const modules = import.meta.globEager('./(sk|en)/**/*.yaml', { assert: { type: 'yaml' } })
const setValueByPath = (obj: any, path: string, value: any, splitChar = '.') => {
const a = path.split(splitChar)
let o = obj
while (a.length - 1) {
const n = a.shift()
if (isUndefined(n)) return
if (!(n in o)) o[n] = {}
o = o[n]
}
o[a[0]] = value
}
const cleanupKey = (key: string) => {
key = key.substring(2)
return key.substring(0, key.indexOf('.'))
}
const final = {}
for (const key in modules) {
const path = cleanupKey(key)
setValueByPath(final, path, modules[key].default, '/')
}
export const messages = final
// @/utils/object.js
then just import messages to i18n config.
then file with this path @/locales/en/blog/article.json with content (note first folder is lang code):
field:
title: Title
body: Bodytext
button:
save: Save
will be transformed to these keys under en language, for example:
const { t } = useI18n({ useScope: 'global' })
t('blog.article.field.title')
t('blog.article.field.body')
t('blog.article.button.save')
i think this can work with json also. i still prefer a solution from authors, for now i can have this as temp solution. just wondering this is still not implemented as a base feature
Yeah, import.meta.globEager (import.meta.glob) is useful.
I would consider implementing it, but it's a proprietary feature that is not standardized in ECMAScript (tc39) or elsewhere.
Since this plugin is for Vite, I would be willing to provide the functionality, but I'm a bit reluctant to rely on a non-standardized feature to implement it 😅
The yaml parser for import.meta.glob behaves poorly.
Specifically, the following YAML fails to parse (checked with YAML Lint and eslint-plugin-yaml):
some-messages:
- first message
- second message
Therefore, I implemented parsing with js-yaml.
import yaml from 'js-yaml';
/** Return value */
const entries: any = {};
// Get locale yaml file.
Object.entries(
import.meta.glob('./(en|ja)/**/*.yml', {
eager: true,
as: 'raw',
})
).forEach(module => {
/** File Path */
const path = module[0];
/** Locale */
const locale = path.slice(2, 4);
/** Key name */
const key = path.slice(5, -4).replace(/\//, '.');
/** Yaml value */
const value = yaml.load(module[1]);
if (key === 'index') {
// Set index.yml to root.
entries[locale] = { ...entries[locale], ...value };
} else {
// Otherwise, assign to child object
const entry = {};
entry[key] = value;
entries[locale] = { ...entries[locale], ...entry };
}
});
export const messages = entries;
index.yml is assigned to root.
It's not very clean code, but I think it will work.
The reason was that Vite does not include a yaml parser. https://github.com/vitejs/vite/issues/10318
I use ts and vite import to manage all translation files. So, I can do a lots of things.
//configuration/locale.ts export const supportedLocales = { en: { name: "English" }, "zh-CN": { name: "中文简体" }, "zh-TW": { name: "中文繁體" }, };
export const defaultLocale = "en";
//locale/index.ts import { supportedLocales } from "@/configuration/locale"; import { merge } from "lodash";
const autoImportedLangs: Record<string, () => Promise
for (const path in autoImportedLangs) { const lang: string = path.substring( path.lastIndexOf("/") + 1, path.lastIndexOf(".") ); if (lang in usedLangs) { merge(usedLangs[lang], autoImportedLangs[path]); } }
export default usedLangs;
install the plugin:
import { createI18n } from "vue-i18n"; import { defaultLocale } from "@/configuration/locale"; import messages from "@/locales";
export default createI18n({ locale: defaultLocale, fallbackWarn: false, missingWarn: false, legacy: false, fallbackLocale: defaultLocale, messages, });