intlify / vue-i18n

Vue I18n for Vue 3
https://vue-i18n.intlify.dev/
MIT License
2.2k stars 336 forks source link

function "t" not reactive inside ref object #1396

Open leonardospeca opened 1 year ago

leonardospeca commented 1 year ago

Reporting a bug?

<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();

const columns = ref([
    { name: 'code', title: t('code') },
    { name: 'name', title: t('name') },
    { name: 'description', title: t('description') },
]);
</script>

<template>
<custom-table :columns="columns"/>
</template>

Using i18n in this way, when you change language dinamically, does not update the translation with the new language. If you replace ref with computed it works, but it not have the same behavior, because in this case, if inside the custom-table you want to make something like column.width = 200 you cannot cause is a computed and not a reactive

Expected behavior

When change locale text should change

Reproduction

<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();

const columns = ref([
    { name: 'code', title: t('code') },
    { name: 'name', title: t('name') },
    { name: 'description', title: t('description') },
]);
</script>

<template>
<custom-table :columns="columns"/>
</template>

System Info

System:
    OS: Linux 5.19 Ubuntu 22.04.1 LTS 22.04.1 LTS (Jammy Jellyfish)
    CPU: (16) x64 AMD Ryzen 7 4800H with Radeon Graphics
    Memory: 611.41 MB / 15.00 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 16.13.2 - /usr/local/bin/node
    npm: 9.6.3 - ~/.local/bin/npm
  Browsers:
    Chrome: 109.0.5414.74
    Firefox: 112.0.2

Screenshot

No response

Additional context

No response

Validations

leonardospeca commented 1 year ago

This is why issue #536 is not the solution

MinatoHikari commented 1 year ago

t is a function which returns a string. It will be called when you declaring the ref, not in render function of component. That's mean,

const msg = ref({
    title: t('title')
})

is something like

const msg = ref({
    title: 'a string'
})
leonardospeca commented 1 year ago

Yes I understand. So in your opinion, which one could be a valid solution to this scenario?

MinatoHikari commented 1 year ago

Yes I understand. So in your opinion, which one could be a valid solution to this scenario?

const arr = ref([
    { name: 'code' },
    { name: 'name' },
    { name: 'description' },
])
const columns = computed(() => arr.value.map(i => {
    i.title = t(i.name)
    return i
}));
Myshkouski commented 1 year ago

Try this workaround:

~/composables/useT.ts:


import { UseI18nOptions } from 'vue-i18n'

export const useT = (key: string | number, options: UseI18nOptions = {}) => {
  const { t, locale: localeRef } = useI18n(options)
  return computed(() => {
    // just to trigger reactivity
    unref(localeRef)
    return t(key)
  })
}

~/components/MyComponent.vue:


<template>
  <div>
    <span>{{ textRef }}</span>
  </div>
</template>

<i18n lang="yaml">

ru:
  lorem_ipsum: Что такое Lorem Ipsum?
en:
  lorem_ipsum: What is Lorem Ipsum?

</i18n>

<script setup lang="ts">

import { useT } from '~/composables/useT'

const textRef = useT('lorem_ipsum', { useScope: 'local' })

</script>
Maiquu commented 1 year ago

reactive unwraps ref and computed. You can do something like:

const button = reactive({
  width: 100,
  height: 200,
  label: computed(() => t('submit'))
})
leonardospeca commented 1 year ago

Yes, effectively i was able to resolve doing so:

const columns = ref([
    { name: 'code', title: computed(_ => t('code')) },
    { name: 'name', title: computed(_ => t('name')) },
    { name: 'description', title: computed(_ => t('description')) },
]);

Are there any contraindications to doing this? If not, thanks everyone

MinatoHikari commented 1 year ago

Yes, effectively i was able to resolve doing so:

const columns = ref([
  { name: 'code', title: computed(_ => t('code')) },
  { name: 'name', title: computed(_ => t('name')) },
  { name: 'description', title: computed(_ => t('description')) },
]);

Are there any contraindications to doing this? If not, thanks everyone

when you changing the value of ref, you must fully use computed again

columns.value = [
    { name: 'code', title: computed(_ => t('code')) },
    { name: 'name', title: computed(_ => t('name')) },
    { name: 'description', title: computed(_ => t('description')) },
]
// wrong
columns.value = [
    { name: 'code', title: t('code')) },
    { name: 'name', title: t('name') },
    { name: 'description', title: t('description') },
]

And the title will be a readonly property, which is not so good that even with typescript you can only know title is a string not a Readonly\<string>.

a0s commented 6 months ago

This is the only one that worked with MenuItem for me:

const {t, locale} = useI18n();

const items = computed(() => {
  return <MenuItem[]>[
    {
      label: locale, // for test only
    },
    {
      label: t('message.delete_all_data'),
      icon: 'pi pi-trash',
      command: () => confirmDeleteAllData(),
    },
  ]
})

<Menu ref="menu" id="overlay_menu" :model="items" :popup="true"/>
Unequaled804 commented 5 months ago

reactive unwraps ref and computed. You can do something like:

const button = reactive({
  width: 100,
  height: 200,
  label: computed(() => t('submit'))
})

Thank you for your idea! It seems that the t function still cannot be updated inside a reactive variable for now. However, I used your method and it works! I made some modifications to your approach: I extracted this logic into a new function named tc, which allows it to be reused concisely.

/** Computed Translation */
const tc = (key) => computed(() => t(key));

const envList = reactive([
    { label: tc('env.sandbox'), value: 'sandbox' },
    { label: tc('env.production'), value: 'production' },
]);
a0s commented 5 months ago

@Unequaled804 watch also works in that way:

watch(() => ({
  title: `${t('message.demo')} - ${appName.toUpperCase()} - ${t('message.site_title')}`,
  description: t('message.demo_page_description'),
}), (value) => {
  useSeoMeta(value)
}, {immediate: true})
Dtsiantaris commented 3 months ago

Hello. Is this still not resolved?

Using a computed is a good solution, as long as you have a readonly key.

Maybe plans for resolving it in 10x?

cannap commented 2 weeks ago

any news on this?

MinatoHikari commented 2 weeks ago

any news on this?

as i said above

const a = ref({
    b: t('c')
})

is equal to

const c = t('c')
// typeof c === 'string' => true
// it
// is 
// just 
// a
// string
// which can never be reactive
const a = ref({
    b: c
})

no magic here but run t('c') as a function which is (v:string) => string

twill only be triggered once in setup

you can never get a.b updated when lang being changed.

please use computed

const a = computed(() => {
   b: t('c')
})

in this way t can be triggered each time when lang being changed