QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.25k stars 59 forks source link

Improve/fix `defineLocale` helper to make sure all locales have the correct keys #225

Open gustaveWPM opened 11 months ago

gustaveWPM commented 11 months ago

I find it unfortunate that currently, there is no documentation regarding how to enforce different locales to be exhaustive.

https://next-international.vercel.app/docs/writing-locales

I think we should be able to ensure that every (or just some) locales exhaustively implement an another locale schema.

At the moment, I proceed like this:

export type MakeHomogeneousValuesObjType<Obj extends object, ObjValuesType> = {
  [K in keyof Obj]: Obj[K] extends object ? MakeHomogeneousValuesObjType<Obj[K], ObjValuesType> : ObjValuesType;
};
import DEFAULT_LANGUAGE_OBJ from '@/i18n/locales/fr';
export type VocabBase = typeof DEFAULT_LANGUAGE_OBJ;
type AllowedVocabObjValuesTypes = string;
type VocabObjValue = AllowedVocabObjValuesTypes;

export type VocabType = MakeHomogeneousValuesObjType<VocabBase, VocabObjValue>;

:arrow_up: :information_source: Here, the idea behind VocabObjValue actually echoes with LocaleData: https://github.com/QuiiBz/next-international/issues/221

// locales/fr.ts

export default {
  whatever: {
    a: "J'aime les gaufres",
    b: 'Et le chocolat'
  }
} as const;
// locales/en.ts

import { VocabType } from '@/types/i18n';

export default {
  whatever: {
    a: 'I like waffles',
    b: 'And chocolate'
  }
} satisfies VocabType; // * ... LOL, this "satisfies" operator is so cute!

So, MakeVocabType could be:

export type MakeVocabType<Locale extends object> = {
  [K in keyof Locale]: Locale[K] extends object ? MakeVocabType<Locale[K]> : LocaleData;
};

And used like:

// * ... import { MakeVocabType } from '...'

import DEFAULT_LANGUAGE_OBJ from '@/i18n/locales/fr';
type VocabBase = typeof DEFAULT_LANGUAGE_OBJ;

export type VocabType = MakeVocabType<VocabBase>;

Also, idk if MakeVocabType would be a good name for this. I guess you would prefer to name this CreateVocabType, as the CreateParams type in international-types.

QuiiBz commented 10 months ago

You might be able to use the defineLocale() helper (from createI18nClient), though it probably doesn't work well with nested-objects locales. It hasn't been touched and used since a long time, but the main idea is here.

We'll need to make it work with both dot-notation and object-notation locales, and add it to the documentation.