kazupon / vue-i18n

:globe_with_meridians: Internationalization plugin for Vue.js
https://kazupon.github.io/vue-i18n/
MIT License
7.28k stars 861 forks source link

Key is not displayed when I change the local and no translation is found (default behavior) #1376

Closed vricosti closed 3 years ago

vricosti commented 3 years ago

I am using vue-i18n (8.25.0) with the following configuration:

main.js

// translations //
import { i18n } from '@/plugins/i18n'
import { Trans } from '@/plugins/Translation'

Trans.init(); // Call a rest api to get current language and change language
Vue.prototype.$i18nRoute = Trans.i18nRoute.bind(Trans)

Vue.config.productionTip = false;
new Vue({
  el: "#app",
  router,
  i18n,
  render: h => h(App)
}).$mount("#app");

plugins/Translations.js:

import axios from 'axios'
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from '@/constants/trans'
import { i18n } from '@/plugins/i18n'

const Trans = {

  get defaultLanguage () {
    return DEFAULT_LANGUAGE
  },
  get supportedLanguages () {
    console.log("Entering get supportedLanguages: %s", SUPPORTED_LANGUAGES);
    return SUPPORTED_LANGUAGES;
  },
  get currentLanguage () {
    return i18n.locale
  },
  set currentLanguage (lang) {
    i18n.locale = lang
  },
  /**
   * Init translation mechanism
   * @return {String}
   */
   init() {
     //this._supportedLanguages = SUPPORTED_LANGUAGES;
     //console.log(_supportedLanguages)
    axios.get("http://localhost:9600/api/shared/language")
        .then(response => {
          const lang = response.data.value.replace(/_/g, "-")
          console.log("lang: %s", lang);
          Trans.changeLanguage(lang);
        }).catch(error => {
          console.log(error)
          this.errored = true
        })
   },
  /**
   * Gets the first supported language that matches the user's
   * @return {String}
   */
  getUserSupportedLang () {
    const userPreferredLang = Trans.getUserLang()

    // Check if user preferred browser lang is supported
    if (Trans.isLangSupported(userPreferredLang.lang)) {
      return userPreferredLang.lang
    }
    // Check if user preferred lang without the ISO is supported
    if (Trans.isLangSupported(userPreferredLang.langNoISO)) {
      return userPreferredLang.langNoISO
    }
    return Trans.defaultLanguage
  },
  /**
   * Returns the users preferred language
   */
  getUserLang () {
    const lang = window.navigator.language || window.navigator.userLanguage || Trans.defaultLanguage
    return {
      lang: lang,
      langNoISO: lang.split('-')[0]
    }
  },
  /**
   * Sets the language to various services (axios, the html tag etc)
   * @param {String} lang
   * @return {String} lang
   */
  setI18nLanguageInServices (lang) {
    Trans.currentLanguage = lang
    axios.defaults.headers.common['Accept-Language'] = lang
    document.querySelector('html').setAttribute('lang', lang)
    return lang
  },
  /**
   * Loads new translation messages and changes the language when finished
   * @param lang
   * @return {Promise<any>}
   */
  changeLanguage (lang) {
    console.log("Entering changeLanguage(%s)", lang);
    if (!Trans.isLangSupported(lang)) return Promise.reject(new Error('Language not supported'))
    if (i18n.locale === lang) return Promise.resolve(lang) // has been loaded prior
    return Trans.loadLanguageFile(lang).then(msgs => {
      console.log("loadLanguageFile ok: %s", lang);
      i18n.setLocaleMessage(lang, msgs.default || msgs)
      return Trans.setI18nLanguageInServices(lang)
    })
  },
  /**
   * Async loads a translation file
   * @param lang
   * @return {Promise<*>|*}
   */
  loadLanguageFile (lang) {
    return import(/* webpackChunkName: "lang-[request]" */ `@/lang/${lang}.json`)
  },
  /**
   * Checks if a lang is supported
   * @param {String} lang
   * @return {boolean}
   */
  isLangSupported (lang) {
    console.log("Entering isLangSupported(%s)", Trans.supportedLanguages);
    return Trans.supportedLanguages.includes(lang)
  },
  /**
   * Checks if the route's param is supported, if not, redirects to the first supported one.
   * @param {Route} to
   * @param {Route} from
   * @param {Function} next
   * @return {*}
   */
  routeMiddleware (to, from, next) {
    // Load async message files here
    const lang = to.params.lang
    if (!Trans.isLangSupported(lang)) return next(Trans.getUserSupportedLang())
    return Trans.changeLanguage(lang).then(() => next())
  },
  /**
   * Returns a new route object that has the current language already defined
   * To be used on pages and components, outside of the main \ route, like on Headers and Footers.
   * @example <router-link :to="$i18nRoute({ name: 'someRoute'})">Click Me </router-link>
   * @param {Object} to - route object to construct
   */
  i18nRoute (to) {
    return {
      ...to,
      params: { lang: this.currentLanguage, ...to.params }
    }
  }
}

export { Trans }

plugins/i18n.js

import VueI18n from 'vue-i18n'
import Vue from 'vue'
import { DEFAULT_LANGUAGE, FALLBACK_LANGUAGE } from '@/constants/trans'
import en_US from '@/lang/en-US.json'

Vue.use(VueI18n)
export const i18n = new VueI18n({
  locale: DEFAULT_LANGUAGE, // set locale
  fallbackLocale: FALLBACK_LANGUAGE,
  messages: { en_US }// set locale messages
})

I have 2 locales en-US and fr-FR where fr-FR has no translations for now. lang/en-US.json

{
  "username": "",
  "password": ""
}

lang/fr-FR.json

{
  "username": "",
  "password": ""
}

Inside main.js Trans.Init is called to query the wanted locale from a rest API then changeLanguage then setI18nLanguageInServices. Once the language has been changed in fr-FR for instance (ie setI18nLanguageInServices has returned) all my texts are empty because it doesn't take the key if not translation is found. On the contrary if I comment inside setI18nLanguageInServices the line Trans.currentLanguage = lang then the texts are in english and not in french but at least the key is displayed. So why once the language is changed it doesn't display the key if no translation is found (default behavior)

vricosti commented 3 years ago

Ok some I kept on searching for my issue and there was some errors in my code with bad naming en-US and en_US so now I have fixed it actually the default behavior doesnt work at all as I thought. When a json file exists and a key has an empty translation (let's say username) it displays an empty string but is it possible in this case to at least display the key itself because displaying something is better than empty ? Even better when a translation is empty (let's say in japanese) would it be possible to have a fallback to take english translation and if english is not translated to take the key ? Is it supported ?

vricosti commented 3 years ago

Actually my question is: is it possible to have a fallback not when a translation is not available but when it's empty ?

exoego commented 3 years ago

What is a motivation to use empty string instead of no key (undefined)?

kazupon commented 3 years ago

Close due to in-activity if you still have the issue, please you would be possible open as a new issue. When you do, please give us a clear explanation.

Thanks!

Sarafaniuk commented 1 year ago

Missing values for existing keys In addition to the above, if you want missing values to fallback to the key in cases where the keys (e.g. got extracted by a code parser) exist in your JSON translation file with empty string as value, you also need this setting:

returnEmptyString: false

thanhtutzaw commented 6 months ago

In addition to the above, if you want missing values to fallback to the key in cases where the keys (e.g. got extracted by a code parser) exist in your JSON tran

Does vue i18n support returnEmptyString: false ? Mine doesn't work.