i18next / react-i18next

Internationalization for react done right. Using the i18next i18n ecosystem.
https://react.i18next.com
MIT License
9.22k stars 1.02k forks source link

Get litteral type of translation path #1280

Closed ghost closed 3 years ago

ghost commented 3 years ago

🚀 Feature Proposal

I want to use a litteral type to allow class to have a translation key. For example : const hello: TranslationKeys = 'common:hello.basic';

Motivation

Actually I use :

export type ObjectPath<T extends Record<string, unknown>> = {
  [P in keyof T]: T[P] extends Record<string, unknown>
    ? `${string & P}` | `${string & P}.${ObjectPath<T[P]>}`
    : `${string & P}`;
}[T extends string[] ? string & keyof T : keyof T];

with

declare module 'react-i18next' {
  type DefaultResources = typeof resources['fr-FR'];
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface Resources extends DefaultResources {}
}

export type TranslationKeys = ObjectPath<typeof resources['fr-FR']>;

But, this gives also incomplete path and typescript gives my errors when I use t(path).

Do you have any idea if it already included or if it planned ? Thanks !

pedrodurek commented 3 years ago

Hey @rwanito, I don't know what you meant by allow class to have a translation key, if it's regarding class-based components using the HoC withTranslation, you can rely on the WithTranslation type.

ghost commented 3 years ago

Hi ! Thanks for your reply ! Sorry, I'll try to be clearer !

I mean by translationKeys, a type with path :

export type TranslationKeys =
  | 'dashboard.page:pageName'
  | 'dashboard.page:smart_stats.c02'
  | 'dashboard.page:smart_stats.speed'
  | 'dashboard.page:smart_stats.time_by_night'
  | 'dashboard.page:smart_stats.time_in_car'
  | 'dashboard.page:smart_stats.under_three'
  | 'dashboard.page:subtitle_last_traject'
  | 'dashboard.page:subtitle_stats'
  | 'dashboard.page:subtitle_stats_cards'
  | 'dashboard.page:subtitle_stats_km'
  | 'dashboard.page:subtitle_stats_score'
  | 'dashboard.page:subtitle_trajects'
  | 'form.validation:format.date'
  | 'form.validation:label.email'
  | 'form.validation:label.firstname'
  | 'form.validation:label.lastname'
  | 'form.validation:label.password'
...

It's impossible to maintain, so I would like to know if it is possible to get this natively ?

Thanks for your work !

pedrodurek commented 3 years ago

Have you tried to import TFuncKey, and do something like this?

type TranslationKeys = TFuncKey<['ns1', 'ns2']>;

Are your namespaces actually dashboard.page, form.validation? 🤔

ghost commented 3 years ago

It seems to be that ! Thanks ! Is it possible to have TFuncKey<*> ? :)

pedrodurek commented 3 years ago

type TranslationKeys = TFuncKey<(keyof Resources)[]>;

ghost commented 3 years ago

Thanks ! It perfectcly what I searched ! I have just an error or warning : Type instantiation is excessively deep and possibly infinite.ts(2589)

I have only 2 levels tree on my namespace and it not excessives.

import { Resources, TFuncKey } from 'react-i18next';
import { resources } from '~/libs/locales.lib/translations';

declare module 'react-i18next' {
  type DefaultResources = typeof resources['fr-FR'];
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface Resources extends DefaultResources {}
}

export type TranslationKeys = TFuncKey<(keyof Resources)[]>;

EDIT : Oh, my bad... I've just forgotten an import. Worst error message ever ^^

ghost commented 3 years ago

Hi ! It doesn't anymore ! I've updated : i18next to 20.2.1, react-i18next to 11.8.12.

The type TFuncKey does not exist anymore. Is it normal ?

Thanks !

EDIT : It seems to be in the ts4.1 folder but I can't import the type from this.

pedrodurek commented 3 years ago

Hey @rwanito, I guess it may be related to this? 🤔 https://github.com/i18next/react-i18next/issues/1196#issuecomment-814481134 Have you tried to import it using this syntax? import type { TFuncKey } from "react-i18next";

ghost commented 3 years ago

I didn't manage to get it worked ! My babel config is pretty simple :

module.exports = (api) => {
  api.cache(true);

  return {
    presets: ['babel-preset-expo'],
    plugins: [
      [
        'module-resolver',
        {
          alias: {
            '~': './src'
          },
          extensions: ['.ts', '.tsx', '.json']
        }
      ]
    ]
  };
};

and my tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "allowSyntheticDefaultImports": true,
    "jsx": "react-native",
    "lib": ["dom", "esnext"],
    "moduleResolution": "node",
    "noEmit": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./src/*"]
    },
    "strictFunctionTypes": false,
    "strictNullChecks": true
  },
  "exclude": [
    "node_modules",
    "__tests__/**/*-test.ts"
  ],
  "include": [
    ".", "./**/*.*", "src/views/assets/svg.assets/trafic", "src/views/assets/svg.assets/time", "src/views/assets/svg.assets/speed", "src/views/assets/svg.assets/sad", "src/views/assets/svg.assets/rural", "src/views/assets/svg.assets/road", "src/views/assets/svg.assets/localisation", "src/views/assets/svg.assets/line", "src/views/assets/svg.assets/limitation", "src/views/assets/svg.assets/highway", "src/views/assets/svg.assets/happy", "src/views/assets/svg.assets/graduation", "src/views/assets/svg.assets/car", "src/views/assets/svg.assets/bike"
  ]
}

The full error message is : Module '"react-i18next"' has no exported member 'TFuncKey'.ts(2305). I precise that versions are the lasted versions of i18next and react-i18next both.

When I investigate, i've found that TFuncKey is defined only on react-i18next/ts4.1 not on the root path. It doen't work too if I do : import type { Resources, TFuncKey } from 'react-i18next/ts4.1';

My TS version is the last stable one.

Thanks for your help !

pedrodurek commented 3 years ago

Hey @rwanito, where is your type declaration file placed?

ghost commented 3 years ago

Hi ! My ts file is in : ./src/ts/modules/locales.module.ts.

import type { Resources, TFuncKey } from 'react-i18next';
import { resources } from '~/libs/locales.lib/translations';

declare module 'react-i18next' {
  type DefaultResources = typeof resources['fr-FR'];
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface Resources extends DefaultResources {}
}

export type TranslationKeys = TFuncKey<(keyof Resources)[]>;

This is here that I have : Module '"react-i18next"' has no exported member 'TFuncKey'.ts(2305).

Thanks !

pedrodurek commented 3 years ago

Hey @rwanito, I've tried to replicate the error with the same ts config, but I didn't manage it. https://codesandbox.io/s/tfunckey-test-7tbm3?file=/src/App.tsx I know the example above it's not a react-native app, but that shouldn't matter.

If you could create a minimalist version of your app (just the setup with i18n in place), that'd help us to understand what's happening on your side.

Small tip (not related to your error): If you place your TranslationKeys inside the declare module, you can import it from react-i18next.

import type { TFuncKey } from 'react-i18next';
import { resources } from '~/libs/locales.lib/translations';

declare module 'react-i18next' {
  type DefaultResources = typeof resources['fr-FR'];
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface Resources extends DefaultResources {}
  type TranslationKeys = TFuncKey<(keyof Resources)[]>;
}
// ./App.tsx
import type { TranslationKeys } from 'react-i18next';
ghost commented 3 years ago

Hi !

I've updated my dev deps : babel, eslint, typescript and It works ! Strange but it works.

  "devDependencies": {
    "@babel/core": "~7.13.15",
    "@types/react": "~16.9.35",
    "@types/react-native": "~0.63.2",
    "@types/react-redux": "^7.1.15",
    "@types/uuid": "^8.3.0",
    "@typescript-eslint/eslint-plugin": "~4.21.0",
    "@typescript-eslint/parser": "~4.21.0",
    "babel-plugin-module-resolver": "^4.1.0",
    "eslint": "~7.24.0",
    "eslint-config-airbnb": "18.2.1",
    "eslint-config-airbnb-typescript": "^12.3.1",
    "eslint-config-prettier": "~8.1.0",
    "eslint-import-resolver-babel-module": "^5.1.2",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.3.0",
    "eslint-plugin-prettier": "^3.3.1",
    "eslint-plugin-react": "^7.23.2",
    "eslint-plugin-react-hooks": "4.2.0",
    "prettier": "^2.2.1",
    "standard-version": "^9.2.0",
    "standard-version-expo": "^1.0.3",
    "typescript": "~4.2.4"
  },

Thanks for the tip ! ;)

It's very strange that babel, eslint...etc impact vscode for importations.

Thanks again pour your help !