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

Any way to use this with TypeScript and not have "as string" all over the codebase? #410

Closed ffxsam closed 3 years ago

ffxsam commented 6 years ago

This is part question, part feature request.

In my code, I constantly have to typecast:

this.$notify.error({
  title: this.$t('auth.invalidSignupTitle') as string,
  message: this.$t('general.error') as string,
});

If I don't, I get this error: Type 'TranslateResult' is not assignable to type 'string'

Is there some workaround for this? Maybe something we can declare once, so it always returns string?

Barry-Fisher commented 6 years ago

I'm not a maintainer but I've just looked at the index.d.ts file and it appears that the expected return value of the t/$t methods is VueI18n.TranslateResult which in turn is defined as type TranslateResult = string | LocaleMessages;

LocaleMessages is defined as interface LocaleMessages { [key: string]: LocaleMessageObject; } which is an indexable type, which strictly could be empty.

This means that your consuming code will yell at you if a string is expected - e.g. values in objects, calls to Error() etc as a call to t/$t may not be a string.

Digging a bit deeper into the t method call chain (in src/index.ts) it appears that it actually won't always return a string. This exception to a string type return occurs where this._translate is called inside _t into a prepared into the constant ret which can return null, so may that's why the return type is defined as any. Maybe this is an area for exploration and improvement to be stricter on the types defined so that there is always a string (or at least an empty string) to trickle back up the call chain to ensure the return value for t is always a string. In particular, there is a line in _translate which checks !isNull(res). I wonder if this can be refactored to a !== '' check instead.

The thing with typescript is that it enforces best practices with return values and it's best not to mix them wherever possible. For example if a function returns a string for its happy path then the null-ish value ought to be an empty string; not null or false or anything else.

Anyway, just my 2 cents. I don't really have a solution to offer but I hope this information is useful if one of the maintainers are able to investigate this.

darthf1 commented 5 years ago

Is this still on the radar? :)

LbISS commented 5 years ago

Still thousands ' as string'-s in thousands of projects... :)

ffxsam commented 5 years ago

Quick note, using .toString() also works, and is probably more correct than type-casting.

LbISS commented 5 years ago

In the end i have used patch-package.

yarn add patch package

Then changed type TranslateResult = string | LocaleMessages; to type TranslateResult = string; Then npx patch-package vue-i18n And to automatically patch typings for all team members added to postinstall in package.json:

"scripts": {
    "postinstall": "patch-package"
}

It'll not work in ALL cases, but it's working for me.

alexeigs commented 5 years ago

You could also use $tc instead of $t whenever you just need the translation without further ado as this function returns a string always.

profispojka commented 4 years ago

use this interfaces.d.ts filein root of your projecz

// Extend Vue with our custom types import Vue from 'vue' declare module 'vue/types/vue' { interface Vue { // == translations $t: (name: string, attrs?: any) => string $tc: (name: string, count: number, attrs?: any) => string } }

// Needs to be here export {}

piktur commented 4 years ago
// vue-i18n.d.ts

import VueI18n, {
  Path, Values, Locale,
} from 'vue-i18n/types'

/**
 * Overloads VueI18n interface to avoid needing to cast return value to string.
 * @see https://github.com/kazupon/vue-i18n/issues/410
 */
declare module 'vue-i18n/types' {
  export default class VueI18n {
    t(key: Path, locale: Locale, values?: Values): string;
    t(key: Path, values?: Values): string;
  }
}

declare module 'vue/types/vue' {
  interface Vue {
    $t: typeof VueI18n.prototype.t;
  }

  interface VueConstructor<V extends Vue = Vue> {
    i18n: typeof VueI18n.prototype;
  }
}

export {}

Did the trick for me.

antpv commented 4 years ago

Just importing TranslateResult type from i18 library.

Zummek commented 4 years ago

@piktur Thank its working but now I had a problem with This expression is not constructable. or No overload matches this call.... after import 'vue-i18n' in .ts. I fixed this with changed last Your line

// vue-i18n.d.ts

import VueI18n, {
  Path, Values, Locale,
} from 'vue-i18n/types'

/**
 * Overloads VueI18n interface to avoid needing to cast return value to string.
 * @see https://github.com/kazupon/vue-i18n/issues/410
 */
declare module 'vue-i18n/types' {
  export default class VueI18n {
    t(key: Path, locale: Locale, values?: Values): string;
    t(key: Path, values?: Values): string;
  }
}

declare module 'vue/types/vue' {
  interface Vue {
    $t: typeof VueI18n.prototype.t;
  }

  interface VueConstructor<V extends Vue = Vue> {
    i18n: typeof VueI18n.prototype;
  }
}

- export {}
+ export default VueI18n
piktur commented 4 years ago

Thanks @Zummek

kazupon commented 3 years ago

we supported in Vue I18n v9. translation function return string. please try it!

Glandos commented 3 years ago

Except that, if I understand correctly, Vue i18n v9 is only compatible with Vue.js 3 and more. So this improvement will have to wait a loooong time before being mass tested.

M3psipax commented 1 year ago

@piktur Thank its working but now I had a problem with This expression is not constructable. or No overload matches this call.... after import 'vue-i18n' in .ts. I fixed this with changed last Your line

// vue-i18n.d.ts

import VueI18n, {
 Path, Values, Locale,
} from 'vue-i18n/types'

/**
* Overloads VueI18n interface to avoid needing to cast return value to string.
* @see https://github.com/kazupon/vue-i18n/issues/410
*/
declare module 'vue-i18n/types' {
 export default class VueI18n {
   t(key: Path, locale: Locale, values?: Values): string;
   t(key: Path, values?: Values): string;
 }
}

declare module 'vue/types/vue' {
 interface Vue {
   $t: typeof VueI18n.prototype.t;
 }

 interface VueConstructor<V extends Vue = Vue> {
   i18n: typeof VueI18n.prototype;
 }
}

- export {}
+ export default VueI18n

Any idea why this might be completely ignored by my IDE Webstorm? That is to say, the error is still shown. I have other types of my own that do work.

zkerhcy commented 4 months ago

Refer to this shim, create vue-i18n.d.ts to export VueI18n as variable:

import VueI18n from 'vue-i18n/types'
/**
 * Replace default export of vue-i18n
 * @see https://github.com/Microsoft/TypeScript/issues/14080#issuecomment-1050833256
 */
export { VueI18n }

then import it in shims-vue-i18n.d.ts

import {
  Path, Values, Locale
} from 'vue-i18n/types'
import { VueI18n } from './vue-i18n'

/**
 * Overloads VueI18n interface to avoid needing to cast return value to string.
 * @see https://github.com/kazupon/vue-i18n/issues/410
 */
declare module './vue-i18n' {
  interface VueI18n {
    t(key: Path, locale: Locale, values?: Values): string
    t(key: Path, values?: Values): string
  }
}

declare module 'vue/types/vue' {
  interface Vue {
    $t: typeof VueI18n.prototype.t
  }

  interface VueConstructor<V extends Vue = Vue> {
    i18n: typeof VueI18n.prototype
  }
}

export default VueI18n