Skip to content

Commit

Permalink
Improve types of getFixedT
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiodxa committed Aug 7, 2024
1 parent d247a1a commit dfb2309
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 34 deletions.
8 changes: 1 addition & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,7 @@
},
"funding": "https://github.com/sponsors/sergiodxa",
"homepage": "https://github.com/sergiodxa/remix-i18next#readme",
"keywords": [
"remix",
"i18n",
"i18next",
"ssr",
"csr"
],
"keywords": ["remix", "i18n", "i18next", "ssr", "csr"],
"license": "MIT",
"peerDependenciesMeta": {
"@remix-run/cloudflare": {
Expand Down
16 changes: 14 additions & 2 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ describe(RemixI18Next.name, () => {
let backendPlugin: BackendModule = {
type: "backend",
init: () => null,
read: (_language, _namespace, callback) => {
read(_language, _namespace, callback) {
callback(null, {
hello: "Hello {{name, uppercase}}",
user: { age: "My age is {{number}}" },
Expand All @@ -265,7 +265,7 @@ describe(RemixI18Next.name, () => {
init: () => null,
add: () => null,
addCached: () => null,
format: (value, format) => {
format(value, format) {
if (format === "uppercase") return value.toUpperCase();
},
};
Expand Down Expand Up @@ -299,6 +299,7 @@ describe(RemixI18Next.name, () => {

let t = await i18n.getFixedT(request, "common");

// @ts-expect-error - We're not using the typed resources here
expect(t("Hello {{name}}", { name: "Remix" })).toBe("Bonjour Remix");
});

Expand Down Expand Up @@ -396,3 +397,14 @@ describe(RemixI18Next.name, () => {
});
});
});

declare module "i18next" {
interface CustomTypeOptions {
resources: {
common: {
hello: string;
user: { age: string };
};
};
}
}
65 changes: 40 additions & 25 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import type {
} from "@remix-run/server-runtime";
import {
type BackendModule,
type DefaultNamespace,
type FlatNamespace,
type InitOptions,
type KeyPrefix,
type Module,
type Namespace,
type NewableModule,
Expand All @@ -15,7 +18,11 @@ import {
import { getClientLocales } from "./lib/get-client-locales.js";
import { pick } from "./lib/parser.js";

const DEFAULT_NS: Namespace = "translation";
type FallbackNs<Ns> = Ns extends undefined
? DefaultNamespace
: Ns extends Namespace
? Ns
: DefaultNamespace;

export interface LanguageDetectorOption {
/**
Expand Down Expand Up @@ -144,47 +151,55 @@ export class RemixI18Next {
* @param namespaces The namespaces to use for the T function. (Default: `translation`).
* @param options The i18next init options and the key prefix to prepend to translation keys.
*/
async getFixedT<N extends Namespace, KPrefix extends string = "">(
async getFixedT<
N extends
| FlatNamespace
| readonly [FlatNamespace, ...FlatNamespace[]] = DefaultNamespace,
KPrefix extends KeyPrefix<FallbackNs<N>> = undefined,
>(
locale: string,
namespaces?: N,
options?: Omit<InitOptions, "react"> & { keyPrefix?: KPrefix },
): Promise<TFunction<N, KPrefix>>;
async getFixedT<N extends Namespace, KPrefix extends string = "">(
): Promise<TFunction<FallbackNs<N>, KPrefix>>;
async getFixedT<
N extends
| FlatNamespace
| readonly [FlatNamespace, ...FlatNamespace[]] = DefaultNamespace,
KPrefix extends KeyPrefix<FallbackNs<N>> = undefined,
>(
request: Request,
namespaces?: N,
options?: Omit<InitOptions, "react"> & { keyPrefix?: KPrefix },
): Promise<TFunction<N, KPrefix>>;
async getFixedT<N extends Namespace, KPrefix extends string = "">(
): Promise<TFunction<FallbackNs<N>, KPrefix>>;
async getFixedT<
N extends
| FlatNamespace
| readonly [FlatNamespace, ...FlatNamespace[]] = DefaultNamespace,
KPrefix extends KeyPrefix<FallbackNs<N>> = undefined,
>(
requestOrLocale: Request | string,
namespaces?: N,
options: Omit<InitOptions, "react"> & { keyPrefix?: KPrefix } = {},
): Promise<TFunction<N, KPrefix>> {
let parsedNamespaces = namespaces ?? DEFAULT_NS;
// Make sure there's at least one namespace
if (!namespaces || namespaces.length === 0) {
parsedNamespaces = (this.options.i18next?.defaultNS ||
"translation") as N;
}

): Promise<TFunction<FallbackNs<N>, KPrefix>> {
let [instance, locale] = await Promise.all([
this.createInstance({
...this.options.i18next,
...options,
fallbackNS: parsedNamespaces,
defaultNS:
typeof parsedNamespaces === "string"
? parsedNamespaces
: parsedNamespaces[0],
}),
this.createInstance({ ...this.options.i18next, ...options }),
typeof requestOrLocale === "string"
? requestOrLocale
: this.getLocale(requestOrLocale),
]);

await instance.changeLanguage(locale);
await instance.loadNamespaces(parsedNamespaces);

return instance.getFixedT(locale, parsedNamespaces, options?.keyPrefix);
if (namespaces) await instance.loadNamespaces(namespaces);
else if (instance.options.defaultNS) {
await instance.loadNamespaces(instance.options.defaultNS);
} else await instance.loadNamespaces("translation" as DefaultNamespace);

return instance.getFixedT<N, KPrefix, N>(
locale,
namespaces,
options?.keyPrefix,
);
}

private async createInstance(options: Omit<InitOptions, "react"> = {}) {
Expand Down

0 comments on commit dfb2309

Please sign in to comment.