Skip to content
This repository has been archived by the owner on Dec 31, 2024. It is now read-only.

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

Closed
ffxsam opened this issue Aug 15, 2018 · 15 comments
Labels
Priority: High Type: Breaking Includes backwards-incompatible fixes

Comments

@ffxsam
Copy link

ffxsam commented Aug 15, 2018

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
Copy link

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
Copy link

darthf1 commented Aug 2, 2019

Is this still on the radar? :)

@LbISS
Copy link

LbISS commented Sep 4, 2019

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

@ffxsam
Copy link
Author

ffxsam commented Sep 4, 2019

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

@LbISS
Copy link

LbISS commented Sep 5, 2019

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
Copy link

alexeigs commented Oct 4, 2019

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
Copy link

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
Copy link

piktur commented Jan 22, 2020

// 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.

derat added a commit to derat/ascenso that referenced this issue Feb 19, 2020
Add an alternate TypeScript definition for vue-i18n's t()
function to avoid needing to call toString() all over the
place to convert LocaleMessages objects to strings.

The file came from this comment:
kazupon/vue-i18n#410 (comment)
@kazupon kazupon added Priority: High Type: Improvement Includes backwards-compatible fixes and removed Type:Typing labels Feb 25, 2020
@kazupon kazupon added Type: Breaking Includes backwards-incompatible fixes and removed Type: Improvement Includes backwards-compatible fixes labels Mar 11, 2020
@antpv
Copy link

antpv commented May 27, 2020

Just importing TranslateResult type from i18 library.

@Zummek
Copy link

Zummek commented Aug 20, 2020

@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
Copy link

piktur commented Oct 24, 2020

Thanks @Zummek

@kazupon
Copy link
Owner

kazupon commented Mar 5, 2021

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

@Glandos
Copy link
Contributor

Glandos commented Mar 22, 2021

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
Copy link

M3psipax commented Aug 3, 2023

@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
Copy link

zkerhcy commented Jul 16, 2024

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Priority: High Type: Breaking Includes backwards-incompatible fixes
Projects
None yet
Development

No branches or pull requests