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

Type Check Dictionaries with TypeScript #1337

Closed
wzup opened this issue Oct 8, 2019 · 5 comments
Closed

Type Check Dictionaries with TypeScript #1337

wzup opened this issue Oct 8, 2019 · 5 comments

Comments

@wzup
Copy link

wzup commented Oct 8, 2019

Is there a possibility to type check existing keys in dictionaries? And throw TS error if key doesn't exist. Default behavior doesn't do it.

Here is what I mean.
Suppose, we have this dictionary:

{
  "footer": {
    "copyright": "Some copyrights"
  },

  "header": {
    "logo": "Logo",
    "link": "Link",
  },
}

If I provide non-existent key, TS should blow up:

const { t } = useTranslation();

<span> { t('footer.copyright') } </span> // this is OK, because footer.copyright exists
<span> { t('footer.logo') } </span> // TS BOOM!! there is no footer.logo in dictionary

What is the proper name of this technique? I'm very sure I'm not the only one who is asking for this behavior.
Is it implemented in this library?
Are there API to extend the library somehow to enable it?

Questions should be posted on StackOverflow

https://stackoverflow.com/questions/58277973/how-to-type-check-i18n-dictionaries-with-typescript

@jamuhl
Copy link
Member

jamuhl commented Oct 8, 2019

@rosskevin
Copy link
Collaborator

While this would be nice to have, I don't see how it would be possible, at least in my use case (and most use cases).

My use case: I use @alienfast/i18next-loader to put together multiple overrides of i18n resources at build time for multiple libraries - meaning that the smaller/underlying libraries have no knowledge or access to the final i18n resources structure. Therefore, building these libraries would be impossible since the i18n resources aren't statically known until building the final composite app.

The reference in the other issue to https://spin.atomicobject.com/2017/12/19/type-check-polyglot-dictionary/ is a possible use case, but I do not see it being the norm. I suspect that at least 80% of our use cases do not know the static structure of the i18n resources. I could certainly be wrong.

The alternatives to static typing the i18n resources include configuring i18next to throw when resources are not found, and exercise components with something like storybook/chromatic, or jest etc. This definitely is not as convenient as static typing, but again, static typing here seems impossible for most cases.

In conclusion, while I would not be against adding the potential for static tsc typing of i18n resources, it is not the majority of use cases that would use such a thing and therefore this is not really on the radar.

We will definitely consider a PR submitted for this feature that is accompanied by the appropriate tests. If you are interested in contributing such a PR, please do so and tag me.

@andre-krueger
Copy link

andre-krueger commented Jun 10, 2020

For anyone wondering how to approach this (in a hacky way):

import de from '@languages/de';
import en from '@languages/en';
import { TOptions, StringMap } from 'i18next';
import { WithTranslation as IWithTranslation } from 'react-i18next';

type Modify<T, R> = Omit<T, keyof R> & R;

export type TranslationKeys =
  | typeof de
  | typeof en;

type TFunctionResult =
  | string
  | object
  | Array<string | object>
  | undefined
  | null;

interface TFunction {
  <
    TResult extends TFunctionResult = string,
    TInterpolationMap extends object = StringMap
  >(
    key: keyof TranslationKeys,
    options?: TOptions<TInterpolationMap> | string,
  ): TResult;
  <
    TResult extends TFunctionResult = string,
    TInterpolationMap extends object = StringMap
  >(
    key: keyof TranslationKeys,
    defaultValue?: string,
    options?: TOptions<TInterpolationMap> | string,
  ): TResult;
}

export type WithTranslation = Modify<
  IWithTranslation,
  {
    t: TFunction;
  }
>;

I basically copied the Interface definition from i18next/index.d.ts and exchanged the key property with the keys from the translation files.

As I'm not too knowledgable in TypeScript I have the following question: Is it possible to implement this in the configuration of i18next, so that one only has to register the translation files to get the autocompletion and type checking which my code currently does?

@LFDM
Copy link

LFDM commented Sep 29, 2020

Given the dynamic nature of nested keys and namespaces, I think there's no way around generating type definitions, and applying them in a similar fashion as @akrger has shown.

I just published a package on npm helping with that.

=> i18next-typescript

It generally works well - unfortunately it's a bit nasty to override all these types, as the original type definition for e.g. TFunction is not generic.

It would be a great help if the main library could make TFunction and all its depdendents generic - I think this could also be done in a backwards compatible, by providing a sensible default for an absent generic type of TFunction (what everyone is usin at the moment). @rosskevin would you be interested in allowing PRs in respect to this?

Here's a GIF, taken from the README of the library to see it in action:

README

@rosskevin
Copy link
Collaborator

@LFDM given that this is not a straightforward case for static analysis, I don't think we want to accept big changes here for stability and maintainability sake. If it proves simple, we want it here.

With that said, your suggestion is a good one. If there is something we can do here to better allow for augmentation and extensibility, I'm all for inclusion of a PR. BTW - take a look at #1504

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