Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@intlify/vite-plugin-vue-i18n locale translation files splitting #97

Open
3 tasks done
volarname opened this issue Mar 18, 2022 · 6 comments
Open
3 tasks done

@intlify/vite-plugin-vue-i18n locale translation files splitting #97

volarname opened this issue Mar 18, 2022 · 6 comments

Comments

@volarname
Copy link

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:

  1. more complex way - first directory will define the locale and each directory is a subject path, so if locales/en/group1/subject1.yaml content is this:
foo: foo
bar:
  baz: baz

the "generated" file for en locale will be:

group1:
  subject1:
    foo: foo
    bar:
      baz: baz
  1. 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

  • Read the Contributing Guidelines.
  • Read the README
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
@volarname volarname added the Status: Proposal Request for comments label Mar 18, 2022
@AngadSethi
Copy link

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.

@volarname
Copy link
Author

volarname commented Apr 1, 2022

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

@kazupon
Copy link
Member

kazupon commented Sep 24, 2022

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 😅

@logue
Copy link

logue commented Sep 24, 2022

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.

@logue
Copy link

logue commented Oct 4, 2022

The reason was that Vite does not include a yaml parser.
vitejs/vite#10318

@velly
Copy link

velly commented Nov 29, 2022

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> = import.meta.glob(
["./.ts", "/src/components/**/lang/.ts"],
{
import: "default",
eager: true,
}
);

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,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants