fix: 修复关闭SSH终端标签页时会话状态未更新的问题

This commit is contained in:
2026-04-18 02:35:38 +08:00
commit 6e2e2f9387
43467 changed files with 5489040 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
22.14.0
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2025 i18next
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+64
View File
@@ -0,0 +1,64 @@
# i18next: learn once - translate everywhere [![Post](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://x.com/intent/tweet?text=Awesome%20i18next:%20learn%20once%20-%20translate%20everywhere%20-%20the%20internationalization%20ecosystem%20&url=https://github.com/i18next/i18next&via=jamuhl&hashtags=i18n,javascript,dev)
[![CI](https://github.com/i18next/i18next/actions/workflows/CI.yml/badge.svg)](https://github.com/i18next/i18next/actions/workflows/CI.yml)
[![Coveralls](https://img.shields.io/coveralls/i18next/i18next/master.svg?style=flat-square)](https://coveralls.io/github/i18next/i18next)
[![Package Quality](https://packagequality.com/shield/i18next.svg)](https://packagequality.com/#?package=i18next)
[![cdnjs version](https://img.shields.io/cdnjs/v/i18next.svg?style=flat-square)](https://cdnjs.com/libraries/i18next)
[![npm version](https://img.shields.io/npm/v/i18next.svg?style=flat-square)](https://www.npmjs.com/package/i18next)
![npm](https://img.shields.io/npm/dw/i18next)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20i18next%20Guru-006BFF)](https://gurubase.io/g/i18next)
i18next is a very popular internationalization framework for browser or any other javascript environment (eg. Node.js, Deno).
![ecosystem](https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-ecosystem.jpg)
i18next provides:
- Flexible connection to [backend](https://www.i18next.com/overview/plugins-and-utils#backends) (loading translations via xhr, ...)
- Optional [caching](https://www.i18next.com/how-to/caching), user [language detection](https://www.i18next.com/overview/plugins-and-utils#language-detector), ...
- Proper [pluralizations](https://www.i18next.com/translation-function/plurals)
- Translation [context](https://www.i18next.com/translation-function/context)
- [Nesting](https://www.i18next.com/translation-function/nesting), [Variable replacement](https://www.i18next.com/translation-function/interpolation)
- Flexibility: [Use it everywhere](https://www.i18next.com/overview/supported-frameworks)
- Extensibility: eg. [sprintf](https://www.i18next.com/overview/plugins-and-utils#post-processors)
- ...
> **Pro Tip:** Looking for a way to manage your translations? Locize is the official service by i18next's creators and now offers a **[Free plan](https://www.locize.com/pricing)** for small projects.
For more information visit the website:
- [Getting started](https://www.i18next.com/overview/getting-started)
- [Translation Functionality](https://www.i18next.com/translation-function/essentials)
- [API](https://www.i18next.com/overview/api)
Our focus is providing the core to building a booming ecosystem. Independent of the building blocks you choose, be it react, angular or even good old jquery proper translation capabilities are just [one step away](https://www.i18next.com/overview/supported-frameworks).
### Documentation
The general i18next documentation is published on [www.i18next.com](https://www.i18next.com) and PR changes can be supplied [here](https://github.com/i18next/i18next-gitbook).
The react specific documentation is published on [react.i18next.com](https://react.i18next.com) and PR changes can be supplied [here](https://github.com/i18next/react-i18next-gitbook).
---
<h3 align="center">Gold Sponsors</h3>
<p align="center">
<a href="https://locize.com/" target="_blank">
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
</a>
</p>
---
**From the creators of i18next: localization as a service - [Locize](https://locize.com)**
A translation management system built around the i18next ecosystem - [Locize](https://locize.com).
**Now with a [Free plan](https://locize.com/pricing) for small projects!** Perfect for hobbyists or getting started.
![Locize](https://www.locize.com/img/ads/github_locize.png)
With using [Locize](https://www.locize.com/?utm_source=i18next_readme&utm_medium=github) you directly support the future of i18next.
---
+2249
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2260
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{"type":"module","version":"25.10.10"}
+2255
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+2255
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+62
View File
@@ -0,0 +1,62 @@
import * as i18nextMod from './index.js';
import type { $Dictionary } from './typescript/helpers.js';
import type { DefaultNamespace, Namespace } from './typescript/options.js';
import type { KeyFromSelectorFn } from './typescript/t.js';
export type WithT<Ns extends Namespace = DefaultNamespace> = i18nextMod.WithT<Ns>;
export type Interpolator = i18nextMod.Interpolator;
export type ResourceStore = i18nextMod.ResourceStore;
export type Formatter = i18nextMod.Formatter;
export type Services = i18nextMod.Services;
export type ModuleType = i18nextMod.ModuleType;
export type Module = i18nextMod.Module;
export type CallbackError = i18nextMod.CallbackError;
export type ReadCallback = i18nextMod.ReadCallback;
export type MultiReadCallback = i18nextMod.MultiReadCallback;
export type BackendModule<TOptions = object> = i18nextMod.BackendModule<TOptions>;
export type LanguageDetectorModule = i18nextMod.LanguageDetectorModule;
export type LanguageDetectorAsyncModule = i18nextMod.LanguageDetectorAsyncModule;
export type PostProcessorModule = i18nextMod.PostProcessorModule;
export type LoggerModule = i18nextMod.LoggerModule;
export type I18nFormatModule = i18nextMod.I18nFormatModule;
export type FormatterModule = i18nextMod.FormatterModule;
export type ThirdPartyModule = i18nextMod.ThirdPartyModule;
export type Modules = i18nextMod.Modules;
export type Newable<T> = i18nextMod.Newable<T>;
export type NewableModule<T extends Module> = i18nextMod.NewableModule<T>;
export type Callback = i18nextMod.Callback;
export type ExistsFunction<
TKeys extends string = string,
TInterpolationMap extends object = $Dictionary,
> = i18nextMod.ExistsFunction<TKeys, TInterpolationMap>;
export type CloneOptions = i18nextMod.CloneOptions;
export type * from './typescript/options.js';
export type * from './typescript/t.js';
export interface CustomInstanceExtensions {}
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface i18n extends i18nextMod.i18n, CustomInstanceExtensions {}
declare const i18next: i18n;
export default i18next;
export const createInstance: i18n['createInstance'];
export const dir: i18n['dir'];
export const init: i18n['init'];
export const loadResources: i18n['loadResources'];
export const reloadResources: i18n['reloadResources'];
export const use: i18n['use'];
export const changeLanguage: i18n['changeLanguage'];
export const getFixedT: i18n['getFixedT'];
export const t: i18n['t'];
export const exists: i18n['exists'];
export const setDefaultNamespace: i18n['setDefaultNamespace'];
export const hasLoadedNamespace: i18n['hasLoadedNamespace'];
export const loadNamespaces: i18n['loadNamespaces'];
export const loadLanguages: i18n['loadLanguages'];
export declare const keyFromSelector: KeyFromSelectorFn;
+609
View File
@@ -0,0 +1,609 @@
// Internal Helpers
import type { $Dictionary, $NormalizeIntoArray } from './typescript/helpers.js';
import type {
DefaultNamespace,
FlatNamespace,
FormatFunction,
InitOptions,
InterpolationOptions,
Namespace,
Resource,
ResourceKey,
ResourceLanguage,
TOptions,
TypeOptions,
} from './typescript/options.js';
import type {
KeyPrefix,
KeyPrefixSelector,
TFunction,
KeyFromSelectorFn,
SelectorKey,
} from './typescript/t.js';
export interface WithT<Ns extends Namespace = DefaultNamespace> {
// Expose parameterized t in the i18next interface hierarchy
t: TFunction<Ns>;
}
export interface Interpolator {
init(options: InterpolationOptions, reset: boolean): undefined;
reset(): undefined;
resetRegExp(): undefined;
interpolate(str: string, data: object, lng: string, options: InterpolationOptions): string;
nest(str: string, fc: (...args: any[]) => any, options: InterpolationOptions): string;
}
export class ResourceStore {
constructor(data: Resource, options: InitOptions);
public data: Resource;
public options: InitOptions;
/**
* Gets fired when resources got added or removed
*/
on(event: 'added' | 'removed', callback: (lng: string, ns: string) => void): void;
/**
* Remove event listener
* removes all callback when callback not specified
*/
off(event: 'added' | 'removed', callback?: (lng: string, ns: string) => void): void;
}
export interface Formatter {
init(services: Services, i18nextOptions: InitOptions): void;
add(name: string, fc: (value: any, lng: string | undefined, options: any) => string): void;
addCached(
name: string,
fc: (lng: string | undefined, options: any) => (value: any) => string,
): void;
format: FormatFunction;
}
export interface Services {
backendConnector: any;
i18nFormat: any;
interpolator: Interpolator;
languageDetector: any;
languageUtils: any;
logger: any;
pluralResolver: any;
resourceStore: ResourceStore;
formatter?: Formatter;
}
export type ModuleType =
| 'backend'
| 'logger'
| 'languageDetector'
| 'postProcessor'
| 'i18nFormat'
| 'formatter'
| '3rdParty';
export interface Module {
type: ModuleType;
}
export type CallbackError = Error | string | null | undefined;
export type ReadCallback = (
err: CallbackError,
data: ResourceKey | boolean | null | undefined,
) => void;
export type MultiReadCallback = (err: CallbackError, data: Resource | null | undefined) => void;
/**
* Used to load data for i18next.
* Can be provided as a singleton or as a prototype constructor (preferred for supporting multiple instances of i18next).
* For singleton set property `type` to `'backend'` For a prototype constructor set static property.
*/
export interface BackendModule<Options = object> extends Module {
type: 'backend';
init(services: Services, backendOptions: Options, i18nextOptions: InitOptions): void;
read(language: string, namespace: string, callback: ReadCallback): void;
/** Save the missing translation */
create?(
languages: readonly string[],
namespace: string,
key: string,
fallbackValue: string,
): void;
/** Load multiple languages and namespaces. For backends supporting multiple resources loading */
readMulti?(
languages: readonly string[],
namespaces: readonly string[],
callback: MultiReadCallback,
): void;
/** Store the translation. For backends acting as cache layer */
save?(language: string, namespace: string, data: ResourceLanguage): void;
}
/**
* Used to detect language in user land.
* Can be provided as a singleton or as a prototype constructor (preferred for supporting multiple instances of i18next).
* For singleton set property `type` to `'languageDetector'` For a prototype constructor set static property.
*/
export interface LanguageDetectorModule extends Module {
type: 'languageDetector';
init?(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
/** Must return detected language */
detect(): string | readonly string[] | undefined;
cacheUserLanguage?(lng: string): void;
}
/**
* Used to detect language in user land.
* Can be provided as a singleton or as a prototype constructor (preferred for supporting multiple instances of i18next).
* For singleton set property `type` to `'languageDetector'` For a prototype constructor set static property.
*/
export interface LanguageDetectorAsyncModule extends Module {
type: 'languageDetector';
/** Set to true to enable async detection */
async: true;
init?(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
/** Must call callback passing detected language or return a Promise */
detect(
callback: (lng: string | readonly string[] | undefined) => void | undefined,
): void | Promise<string | readonly string[] | undefined>;
cacheUserLanguage?(lng: string): void | Promise<void>;
}
/**
* Used to extend or manipulate the translated values before returning them in `t` function.
* Need to be a singleton object.
*/
export interface PostProcessorModule extends Module {
/** Unique name */
name: string;
type: 'postProcessor';
process(value: string, key: string | string[], options: TOptions, translator: any): string;
}
/**
* Override the built-in console logger.
* Do not need to be a prototype function.
*/
export interface LoggerModule extends Module {
type: 'logger';
log(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
}
export interface I18nFormatModule extends Module {
type: 'i18nFormat';
}
export interface FormatterModule extends Module, Formatter {
type: 'formatter';
}
export interface ThirdPartyModule extends Module {
type: '3rdParty';
init(i18next: i18n): void;
}
export interface Modules {
backend?: BackendModule;
logger?: LoggerModule;
languageDetector?: LanguageDetectorModule | LanguageDetectorAsyncModule;
i18nFormat?: I18nFormatModule;
formatter?: FormatterModule;
external: ThirdPartyModule[];
}
// helper to identify class https://stackoverflow.com/a/45983481/2363935
export interface Newable<T> {
new (...args: any[]): T;
}
export interface NewableModule<T extends Module> extends Newable<T> {
type: T['type'];
}
export type Callback = (error: any, t: TFunction) => void;
/**
* Uses similar args as the t function and returns true if a key exists.
* Acts as a type guard, narrowing the key to {@link SelectorKey} so it can be passed to `t()`.
*/
export interface ExistsFunction<
TKeys extends string = string,
TInterpolationMap extends object = $Dictionary,
> {
(key: TKeys, options?: TOptions<TInterpolationMap>): key is TKeys & SelectorKey;
(key: TKeys | TKeys[], options?: TOptions<TInterpolationMap>): boolean;
}
export interface CloneOptions extends InitOptions {
/**
* Will create a new instance of the resource store and import the existing translation resources.
* This way it will not shared the resource store instance.
* @default false
*/
forkResourceStore?: boolean;
}
export interface CustomInstanceExtensions {}
// Used just here to exclude `DefaultNamespace` which can be both string or array from `FlatNamespace`
// in TFunction declaration below.
// Due to this only very special usage I'm not moving this inside helpers.
type InferArrayValuesElseReturnType<T> = T extends (infer A)[] ? A : T;
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface i18n extends CustomInstanceExtensions {
// Expose parameterized t in the i18next interface hierarchy
t: TFunction<
[
...$NormalizeIntoArray<DefaultNamespace>,
...Exclude<FlatNamespace, InferArrayValuesElseReturnType<DefaultNamespace>>[],
]
>;
/**
* The default of the i18next module is an i18next instance ready to be initialized by calling init.
* You can create additional instances using the createInstance function.
*
* @param options - Initial options.
* @param callback - will be called after all translations were loaded or with an error when failed (in case of using a backend).
*/
init(callback?: Callback): Promise<TFunction>;
init<T>(options: InitOptions<T>, callback?: Callback): Promise<TFunction>;
loadResources(callback?: (err: any) => void): void;
/**
* The use function is there to load additional plugins to i18next.
* For available module see the plugins page and don't forget to read the documentation of the plugin.
*
* @param module Accepts a class or object
*/
use<T extends Module>(module: T | NewableModule<T> | Newable<T>): this;
/**
* List of modules used
*/
modules: Modules;
/**
* Internal container for all used plugins and implementation details like languageUtils, pluralResolvers, etc.
*/
services: Services;
/**
* Internal container for translation resources
*/
store: ResourceStore;
/**
* Uses similar args as the t function and returns true if a key exists.
*/
exists: ExistsFunction;
/**
* Returns a resource data by language.
*/
getDataByLanguage(lng: string): { [key: string]: { [key: string]: string } } | undefined;
/**
* Returns a t function that defaults to given language or namespace.
* Both params could be arrays of languages or namespaces and will be treated as fallbacks in that case.
* On the returned function you can like in the t function override the languages or namespaces by passing them in options or by prepending namespace.
*
* Accepts optional keyPrefix that will be automatically applied to returned t function.
*/
getFixedT<
Ns extends Namespace | null,
const TKPrefixFn extends TypeOptions['enableSelector'] extends true | 'optimize'
? KeyPrefixSelector<ActualNs>
: never,
ActualNs extends Namespace = Ns extends null ? DefaultNamespace : Ns,
>(
lng: string | readonly string[] | null,
ns: Ns,
keyPrefix: TKPrefixFn,
): TFunction<ActualNs, TKPrefixFn>;
getFixedT<
Ns extends Namespace | null = DefaultNamespace,
TKPrefix extends KeyPrefix<ActualNs> = undefined,
ActualNs extends Namespace = Ns extends null ? DefaultNamespace : Ns,
>(
...args:
| [lng: string | readonly string[], ns?: Ns, keyPrefix?: TKPrefix | (($: any) => any)]
| [lng: null, ns: Ns, keyPrefix?: TKPrefix | (($: any) => any)]
): TFunction<ActualNs, TKPrefix>;
/**
* Changes the language. The callback will be called as soon translations were loaded or an error occurs while loading.
* HINT: For easy testing - setting lng to 'cimode' will set t function to always return the key.
*/
changeLanguage(lng?: string, callback?: Callback): Promise<TFunction>;
/**
* Is set to the current detected or set language.
* If you need the primary used language depending on your configuration (supportedLngs, load) you will prefer using i18next.languages[0].
*/
language: string;
/**
* Is set to an array of language-codes that will be used it order to lookup the translation value.
*/
languages: readonly string[];
/**
* Is set to the current resolved language.
* It can be used as primary used language, for example in a language switcher.
*/
resolvedLanguage?: string;
/**
* Checks if namespace has loaded yet.
* i.e. used by react-i18next
*/
hasLoadedNamespace(
ns: string | readonly string[],
options?: {
lng?: string | readonly string[];
fallbackLng?: InitOptions['fallbackLng'];
/**
* if `undefined` is returned default checks are performed.
*/
precheck?: (
i18n: i18n,
/**
* Check if the language namespace provided are not in loading status:
* returns `true` if load is completed successfully or with an error.
*/
loadNotPending: (
lng: string | readonly string[],
ns: string | readonly string[],
) => boolean,
) => boolean | undefined;
},
): boolean;
/**
* Loads additional namespaces not defined in init options.
*/
loadNamespaces(ns: string | readonly string[], callback?: Callback): Promise<void>;
/**
* Loads additional languages not defined in init options (preload).
*/
loadLanguages(lngs: string | readonly string[], callback?: Callback): Promise<void>;
/**
* Reloads resources on given state. Optionally you can pass an array of languages and namespaces as params if you don't want to reload all.
*/
reloadResources(
lngs?: string | readonly string[],
ns?: string | readonly string[],
callback?: () => void,
): Promise<void>;
reloadResources(lngs: null, ns: string | readonly string[], callback?: () => void): Promise<void>;
/**
* Changes the default namespace.
*/
setDefaultNamespace(ns: string | readonly string[]): void;
/**
* Returns rtl or ltr depending on languages read direction.
*/
dir(lng?: string): 'ltr' | 'rtl';
/**
* Exposes interpolation.format function added on init.
*/
format: FormatFunction;
/**
* Will return a new i18next instance.
* Please read the options page for details on configuration options.
* Providing a callback will automatically call init.
* The callback will be called after all translations were loaded or with an error when failed (in case of using a backend).
*/
createInstance(options?: InitOptions, callback?: Callback): i18n;
/**
* Creates a clone of the current instance. Shares store, plugins and initial configuration.
* Can be used to create an instance sharing storage but being independent on set language or namespaces.
*/
cloneInstance(options?: CloneOptions, callback?: Callback): i18n;
/**
* Returns a JSON representation of the i18next instance for serialization.
*/
toJSON(): {
options: InitOptions;
store: ResourceStore;
language: string;
languages: readonly string[];
resolvedLanguage?: string;
};
/**
* Gets fired after initialization.
*/
on(event: 'initialized', callback: (options: InitOptions) => void): this;
/**
* Gets fired on loaded resources.
*/
on(
event: 'loaded',
callback: (loaded: { [language: string]: { [namespace: string]: boolean } }) => void,
): this;
/**
* Gets fired if loading resources failed.
*/
on(event: 'failedLoading', callback: (lng: string, ns: string, msg: string) => void): this;
/**
* Gets fired on accessing a key not existing.
*/
on(
event: 'missingKey',
callback: (lngs: readonly string[], namespace: string, key: string, res: string) => void,
): this;
/**
* Gets fired when changeLanguage got called.
*/
on(event: 'languageChanged', callback: (lng: string) => void): this;
/**
* Event listener
*/
on(event: string, listener: (...args: any[]) => void): this;
/**
* Remove event listener
* removes all callback when callback not specified
*/
off(event: string, listener?: (...args: any[]) => void): void;
/**
* Gets one value by given key.
*/
getResource(
lng: string,
ns: string,
key: string,
options?: Pick<InitOptions, 'keySeparator' | 'ignoreJSONStructure'>,
): any;
/**
* Adds one key/value.
*/
addResource(
lng: string,
ns: string,
key: string,
value: string,
options?: { keySeparator?: string; silent?: boolean },
): i18n;
/**
* Adds multiple key/values.
*/
addResources(lng: string, ns: string, resources: any): i18n;
/**
* Adds a complete bundle.
* Setting deep param to true will extend existing translations in that file.
* Setting overwrite to true it will overwrite existing translations in that file.
*/
addResourceBundle(
lng: string,
ns: string,
resources: any,
deep?: boolean,
overwrite?: boolean,
): i18n;
/**
* Checks if a resource bundle exists.
*/
hasResourceBundle(lng: string, ns: string): boolean;
/**
* Returns a resource bundle.
*/
getResourceBundle(lng: string, ns: string): any;
/**
* Removes an existing bundle.
*/
removeResourceBundle(lng: string, ns: string): i18n;
/**
* Current options
*/
options: InitOptions;
/**
* Is initialized
*/
isInitialized: boolean;
/**
* Is initializing
*/
isInitializing: boolean;
/**
* Store was initialized
*/
initializedStoreOnce: boolean;
/**
* Language was initialized
*/
initializedLanguageOnce: boolean;
/**
* Emit event
*/
emit(eventName: string, ...args: any[]): void;
}
export type * from './typescript/options.js';
export type {
// we need to explicitely export some types, to prevent some issues with next-i18next and interpolation variable validation, etc...
FallbackLngObjList,
FallbackLng,
InitOptions,
TypeOptions,
CustomTypeOptions,
CustomPluginOptions,
PluginOptions,
FormatFunction,
InterpolationOptions,
ReactOptions,
ResourceKey,
ResourceLanguage,
Resource,
TOptions,
Namespace,
DefaultNamespace,
FlatNamespace,
} from './typescript/options.js';
export type * from './typescript/t.js';
export type {
TFunction,
ParseKeys,
TFunctionReturn,
TFunctionDetailedResult,
KeyPrefix,
KeyPrefixSelector,
NsResource,
InterpolationMap,
SelectorKey,
} from './typescript/t.js';
declare const i18next: i18n;
export default i18next;
export const createInstance: i18n['createInstance'];
export const dir: i18n['dir'];
export const init: i18n['init'];
export const loadResources: i18n['loadResources'];
export const reloadResources: i18n['reloadResources'];
export const use: i18n['use'];
export const changeLanguage: i18n['changeLanguage'];
export const getFixedT: i18n['getFixedT'];
export const t: i18n['t'];
export const exists: i18n['exists'];
export const setDefaultNamespace: i18n['setDefaultNamespace'];
export const hasLoadedNamespace: i18n['hasLoadedNamespace'];
export const loadNamespaces: i18n['loadNamespaces'];
export const loadLanguages: i18n['loadLanguages'];
export declare const keyFromSelector: KeyFromSelectorFn;
+5
View File
@@ -0,0 +1,5 @@
/* eslint no-var: 0 */
var main = require('./dist/cjs/i18next.js');
module.exports = main;
module.exports.default = main;
+15
View File
@@ -0,0 +1,15 @@
{
"name": "@i18next/i18next",
"version": "25.10.10",
"license": "MIT",
"exports": "./src/index.js",
"publish": {
"include": [
"src/*.js",
"index.d.ts",
"index.d.mts",
"typescript/*.d.ts",
"README.md"
]
}
}
+127
View File
@@ -0,0 +1,127 @@
{
"name": "i18next",
"version": "25.10.10",
"description": "i18next internationalization framework",
"main": "./dist/cjs/i18next.js",
"module": "./dist/esm/i18next.js",
"types": "./index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./index.d.mts",
"default": "./dist/esm/i18next.js"
},
"require": {
"types": "./index.d.ts",
"default": "./dist/cjs/i18next.js"
}
}
},
"keywords": [
"i18next",
"internationalization",
"i18n",
"translation",
"localization",
"l10n",
"globalization",
"gettext"
],
"homepage": "https://www.i18next.com",
"bugs": "https://github.com/i18next/i18next/issues",
"repository": {
"type": "git",
"url": "https://github.com/i18next/i18next.git"
},
"funding": [
{
"type": "individual",
"url": "https://www.locize.com/i18next"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
},
{
"type": "individual",
"url": "https://www.locize.com"
}
],
"dependencies": {
"@babel/runtime": "^7.29.2"
},
"peerDependencies": {
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"devDependencies": {
"@arktype/attest": "^0.56.0",
"@babel/core": "^7.29.0",
"@babel/plugin-transform-async-generator-functions": "^7.29.0",
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.29.2",
"@babel/preset-react": "^7.28.5",
"@babel/register": "^7.28.6",
"@rollup/plugin-babel": "^7.0.0",
"@rollup/plugin-commonjs": "^29.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^1.0.0",
"@types/node": "^25.5.0",
"@typescript-eslint/eslint-plugin": "^8.57.1",
"@typescript-eslint/parser": "^8.57.1",
"@vitest/coverage-v8": "^3.2.4",
"babelify": "^10.0.0",
"coveralls": "^3.1.1",
"cpy-cli": "^7.0.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"gh-release": "^7.0.2",
"husky": "^9.1.7",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-fs-backend": "^2.6.1",
"i18next-http-backend": "^3.0.2",
"i18next-localstorage-cache": "^1.1.1",
"i18next-sprintf-postprocessor": "^0.2.2",
"lint-staged": "^16.4.0",
"prettier": "^3.8.1",
"rimraf": "^6.1.3",
"rollup": "^4.59.1",
"sinon": "^19.0.5",
"typescript": "^5.9.3",
"vitest": "^3.2.4"
},
"scripts": {
"lint": "eslint src typescript test \"./*.{ts,mts,mjs}\"",
"format": "prettier \"{,**/}*.{ts,tsx,mts,js,mjs,json,md}\" --check",
"format:fix": "prettier \"{,**/}*.{ts,tsx,mts,js,mjs,json,md}\" --write",
"test": "vitest --run",
"test:coverage": "vitest --coverage --run",
"test:runtime": "vitest --project runtime",
"test:compatibility": "vitest --project compatibility",
"test:typescript": "vitest --workspace vitest.workspace.typescript.mts",
"test:local": "vitest --workspace vitest.workspace.local.mts",
"build": "rimraf dist && rollup -c && echo '{\"type\":\"module\"}' > dist/esm/package.json && cpy \"./dist/umd/*.js\" ./",
"fix_dist_package": "node -e 'console.log(`{\"type\":\"module\",\"version\":\"${process.env.npm_package_version}\"}`)' > dist/esm/package.json",
"fix_jsr_package": "node -e 'const fs=require(\"fs\");const p=\"jsr.json\";const j=JSON.parse(fs.readFileSync(p,\"utf8\"));j.version=process.env.npm_package_version||j.version;fs.writeFileSync(p,JSON.stringify(j,null,2)+\"\\n\")' && git commit -a -m 'jsr update' && git push",
"publish_jsr": "deno publish --no-check",
"preversion": "npm run test && npm run build && git push",
"postversion": "npm run fix_dist_package && npm run fix_jsr_package && git push && git push --tags && npm run release && npm run publish_jsr",
"prepare": "husky",
"release": "gh-release"
},
"author": "Jan Mühlemann <jan.muehlemann@gmail.com> (https://github.com/jamuhl)",
"license": "MIT",
"lint-staged": {
"*": "prettier --write --ignore-unknown"
}
}
+77
View File
@@ -0,0 +1,77 @@
// Types
export type $Dictionary<T = unknown> = { [key: string]: T };
export type $SpecialObject = object | Array<string | object>;
// Types Operators
export type $Prune<T> =
| never
| { [K in keyof T as [keyof T[K]] extends [never] ? never : K]: T[K] };
/** All the way down. */
export interface $Turtles {
[x: string]: $Turtles;
}
export type $MergeBy<T, K> = Omit<T, keyof K> & K;
export type $OmitArrayKeys<Arr> = Arr extends readonly any[] ? Omit<Arr, keyof any[]> : Arr;
export type $PreservedValue<Value, Fallback> = [Value] extends [never] ? Fallback : Value;
export type $NormalizeIntoArray<T extends unknown | readonly unknown[]> =
T extends readonly unknown[] ? T : [T];
/**
* @typeParam T
* @example
* ```
* $UnionToIntersection<{foo: {bar: string} | {asd: boolean}}> = {foo: {bar: string} & {asd: boolean}}
* ```
*
* @see https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
*/
type $UnionToIntersection<T> = (T extends unknown ? (k: T) => void : never) extends (
k: infer I,
) => void
? I
: never;
/**
* @typeParam TPath union of strings
* @typeParam TValue value of the record
* @example
* ```
* $StringKeyPathToRecord<'foo.bar' | 'asd'> = {foo: {bar: string} | {asd: boolean}}
* ```
*/
type $StringKeyPathToRecordUnion<
TPath extends string,
TValue,
> = TPath extends `${infer TKey}.${infer Rest}`
? { [Key in TKey]: $StringKeyPathToRecord<Rest, TValue> }
: { [Key in TPath]: TValue };
/**
* Used to intersect output of {@link $StringKeyPathToRecordUnion}
*
* @typeParam TPath union of strings
* @typeParam TValue value of the record
* @example
* ```
* $StringKeyPathToRecord<'foo.bar' | 'asd'> = {foo: {bar: string} & {asd: boolean}}
* ```
*/
export type $StringKeyPathToRecord<TPath extends string, TValue> = $UnionToIntersection<
$StringKeyPathToRecordUnion<TPath, TValue>
>;
/**
* We could use NoInfer typescript build-in utility,
* however this project still supports ts < 5.4.
*
* @see https://github.com/millsp/ts-toolbelt/blob/master/sources/Function/NoInfer.ts
*/
export type $NoInfer<A> = [A][A extends any ? 0 : never];
+833
View File
@@ -0,0 +1,833 @@
import type { $MergeBy, $PreservedValue, $Dictionary } from './helpers.js';
/**
* This interface can be augmented by users to add types to `i18next` default TypeOptions.
*
* Usage:
* ```ts
* // i18next.d.ts
* import 'i18next';
* declare module 'i18next' {
* interface CustomTypeOptions {
* defaultNS: 'custom';
* returnNull: false;
* returnObjects: false;
* nsSeparator: ':';
* keySeparator: '.';
* compatibilityJSON: 'v4';
* allowObjectInHTMLChildren: false;
* resources: {
* custom: {
* foo: 'foo';
* };
* };
* }
* }
* ```
*/
export interface CustomTypeOptions {}
/**
* This interface can be augmented by users to add types to `i18next` default PluginOptions.
*/
export interface CustomPluginOptions {}
export type TypeOptions = $MergeBy<
{
/** @see {InitOptions.returnNull} */
returnNull: false;
/** @see {InitOptions.returnEmptyString} */
returnEmptyString: true;
/** @see {InitOptions.returnObjects} */
returnObjects: false;
/** @see {InitOptions.keySeparator} */
keySeparator: '.';
/** @see {InitOptions.nsSeparator} */
nsSeparator: ':';
/** @see {InitOptions.pluralSeparator} */
pluralSeparator: '_';
/** @see {InitOptions.contextSeparator} */
contextSeparator: '_';
/** @see {InitOptions.defaultNS} */
defaultNS: 'translation';
/** @see {InitOptions.fallbackNS} */
fallbackNS: false;
/** @see {InitOptions.compatibilityJSON} */
compatibilityJSON: 'v4';
/** @see {InitOptions.resources} */
resources: object;
/**
* Flag that allows HTML elements to receive objects. This is only useful for React applications
* where you pass objects to HTML elements so they can be replaced to their respective interpolation
* values (mostly with Trans component)
*/
allowObjectInHTMLChildren: false;
/**
* Flag that enables strict key checking even if a `defaultValue` has been provided.
* This ensures all calls of `t` function don't accidentally use implicitly missing keys.
*/
strictKeyChecks: false;
/**
* Prefix for interpolation
*/
interpolationPrefix: '{{';
/**
* Suffix for interpolation
*/
interpolationSuffix: '}}';
/** @see {InterpolationOptions.unescapePrefix} */
unescapePrefix: '-';
/** @see {InterpolationOptions.unescapeSuffix} */
unescapeSuffix: '';
/**
* Use a proxy-based selector to select a translation.
*
* Enables features like go-to definition, and better DX/faster autocompletion
* for TypeScript developers.
*
* If you're working with an especially large set of translations and aren't
* using context, you set `enableSelector` to `"optimize"` and i18next won't do
* any type-level processing of your translations at all.
*
* With `enableSelector` set to `"optimize"`, i18next is capable of supporting
* arbitrarily large/deep translation sets without causing any IDE slowdown
* whatsoever.
*
* @default false
*/
enableSelector: false;
/**
* Maps interpolation format specifiers to their expected value types.
*
* By default, i18next infers types from built-in formatter names:
* - `number`, `currency` → `number`
* - `datetime` → `Date`
* - `relativetime` → `number`
* - `list` → `readonly string[]`
* - No format specifier → `string`
*
* Use this option to add mappings for custom formatters or to override
* the built-in defaults.
*
* @default {} (empty — built-in defaults apply)
*
* @example
* ```ts
* interface CustomTypeOptions {
* interpolationFormatTypeMap: {
* // custom formatter
* uppercase: string;
* // override built-in
* currency: string;
* };
* }
* ```
*/
interpolationFormatTypeMap: {};
},
CustomTypeOptions
>;
export type PluginOptions<T> = $MergeBy<
{
/**
* Options for language detection - check documentation of plugin
* @default undefined
*/
detection?: object;
/**
* Options for backend - check documentation of plugin
* @default undefined
*/
backend?: T;
/**
* Options for cache layer - check documentation of plugin
* @default undefined
*/
cache?: object;
/**
* Options for i18n message format - check documentation of plugin
* @default undefined
*/
i18nFormat?: object;
},
CustomPluginOptions
>;
export type FormatFunction = (
value: any,
format?: string,
lng?: string,
options?: InterpolationOptions & $Dictionary<any>,
) => string;
export interface InterpolationOptions {
/**
* Format function see formatting for details
* @default noop
*/
format?: FormatFunction;
/**
* Used to separate format from interpolation value
* @default ','
*/
formatSeparator?: string;
/**
* Escape function
* @default str => str
*/
escape?(str: string): string;
/**
* Always format interpolated values.
* @default false
*/
alwaysFormat?: boolean;
/**
* Escape passed in values to avoid xss injection
* @default true
*/
escapeValue?: boolean;
/**
* If true, then value passed into escape function is not casted to string, use with custom escape function that does its own type check
* @default false
*/
useRawValueToEscape?: boolean;
/**
* Prefix for interpolation
* @default '{{'
*/
prefix?: string;
/**
* Suffix for interpolation
* @default '}}'
*/
suffix?: string;
/**
* Escaped prefix for interpolation (regexSafe)
* @default undefined
*/
prefixEscaped?: string;
/**
* Escaped suffix for interpolation (regexSafe)
* @default undefined
*/
suffixEscaped?: string;
/**
* Suffix to unescaped mode
* @default undefined
*/
unescapeSuffix?: string;
/**
* Prefix to unescaped mode
* @default '-'
*/
unescapePrefix?: string;
/**
* Prefix for nesting
* @default '$t('
*/
nestingPrefix?: string;
/**
* Suffix for nesting
* @default ')'
*/
nestingSuffix?: string;
/**
* Escaped prefix for nesting (regexSafe)
* @default undefined
*/
nestingPrefixEscaped?: string;
/**
* Escaped suffix for nesting (regexSafe)
* @default undefined
*/
nestingSuffixEscaped?: string;
/**
* Separates options from key
* @default ','
*/
nestingOptionsSeparator?: string;
/**
* Global variables to use in interpolation replacements
* @default undefined
*/
defaultVariables?: { [index: string]: any };
/**
* After how many interpolation runs to break out before throwing a stack overflow
* @default 1000
*/
maxReplaces?: number;
/**
* If true, it will skip to interpolate the variables
* @default true
*/
skipOnVariables?: boolean;
}
export interface FallbackLngObjList {
[language: string]: readonly string[];
}
export type FallbackLng =
| string
| readonly string[]
| FallbackLngObjList
| ((code: string) => string | readonly string[] | FallbackLngObjList);
export interface ReactOptions {
/**
* Set it to fallback to let passed namespaces to translated hoc act as fallbacks
* @default 'default'
*/
nsMode?: 'default' | 'fallback';
/**
* Set it to the default parent element created by the Trans component.
* @default 'div'
*/
defaultTransParent?: string;
/**
* Set which events trigger a re-render, can be set to false or string of events
* @default 'languageChanged'
*/
bindI18n?: string | false;
/**
* Set which events on store trigger a re-render, can be set to false or string of events
* @default ''
*/
bindI18nStore?: string | false;
/**
* Set fallback value for Trans components without children
* @default undefined
*/
transEmptyNodeValue?: string;
/**
* Set it to false if you do not want to use Suspense
* @default true
*/
useSuspense?: boolean;
/**
* Function to generate an i18nKey from the defaultValue (or Trans children)
* when no key is provided.
* By default, the defaultValue (Trans text) itself is used as the key.
* If you want to require keys for all translations, supply a function
* that always throws an error.
* @default undefined
*/
hashTransKey?(defaultValue: TOptionsBase['defaultValue']): TOptionsBase['defaultValue'];
/**
* Convert eg. <br/> found in translations to a react component of type br
* @default true
*/
transSupportBasicHtmlNodes?: boolean;
/**
* Which nodes not to convert in defaultValue generation in the Trans component.
* @default ['br', 'strong', 'i', 'p']
*/
transKeepBasicHtmlNodesFor?: readonly string[];
/**
* Wrap text nodes in a user-specified element.
* @default ''
*/
transWrapTextNodes?: string;
/**
* Default props to apply to all Trans components.
* Component-level props will override these defaults.
*/
transDefaultProps?: {
tOptions?: TOptions;
values?: object;
shouldUnescape?: boolean;
components?: readonly unknown[] | { readonly [tagName: string]: unknown }; // Use `unknown` (or `any`) to be permissive without importing React.
};
/**
* Optional keyPrefix that will be automatically applied to returned t function in useTranslation for example.
* Accepts a string or a selector function (e.g. `$ => $.deeply.nested`).
* @default undefined
*/
keyPrefix?: string | (($: any) => any);
/**
* Unescape function
* by default it unescapes some basic html entities
*/
unescape?(str: string): string;
}
export type ResourceKey =
| string
| {
[key: string]: any;
};
export interface ResourceLanguage {
[namespace: string]: ResourceKey;
}
export interface Resource {
[language: string]: ResourceLanguage;
}
export interface InitOptions<T = object> extends PluginOptions<T> {
/**
* Logs info level to console output. Helps finding issues with loading not working.
* @default false
*/
debug?: boolean;
/**
* Show support notice in console during initialization.
* @default true
*/
showSupportNotice?: boolean;
/**
* Resources to initialize with (if not using loading or not appending using addResourceBundle)
* @default undefined
*/
resources?: Resource;
/**
* Allow initializing with bundled resources while using a backend to load non bundled ones.
* @default false
*/
partialBundledLanguages?: boolean;
/**
* Language to use (overrides language detection)
* @default undefined
*/
lng?: string;
/**
* Language to use if translations in user language are not available.
* @default 'dev'
*/
fallbackLng?: false | FallbackLng;
/**
* Array of allowed languages
* @default false
*/
supportedLngs?: false | readonly string[];
/**
* If true will pass eg. en-US if finding en in supportedLngs
* @default false
*/
nonExplicitSupportedLngs?: boolean;
/**
* Language codes to lookup, given set language is 'en-US':
* 'all' --> ['en-US', 'en', 'dev'],
* 'currentOnly' --> 'en-US',
* 'languageOnly' --> 'en'
* @default 'all'
*/
load?: 'all' | 'currentOnly' | 'languageOnly';
/**
* Array of languages to preload. Important on server-side to assert translations are loaded before rendering views.
* @default false
*/
preload?: false | readonly string[];
/**
* Language will be lowercased eg. en-US --> en-us
* @default false
*/
lowerCaseLng?: boolean;
/**
* Language will be lowercased EN --> en while leaving full locales like en-US
* @default false
*/
cleanCode?: boolean;
/**
* String or array of namespaces to load
* @default 'translation'
*/
ns?: string | readonly string[];
/**
* Default namespace used if not passed to translation function
* @default 'translation'
*/
defaultNS?: string | false | readonly string[];
/**
* String or array of namespaces to lookup key if not found in given namespace.
* @default false
*/
fallbackNS?: false | string | readonly string[];
/**
* Calls save missing key function on backend if key not found.
* @default false
*/
saveMissing?: boolean;
/**
* Calls save missing key function on backend if key not found also for plural forms.
* @default false
*/
saveMissingPlurals?: boolean;
/**
* Experimental: enable to update default values using the saveMissing
* (Works only if defaultValue different from translated value.
* Only useful on initial development or when keeping code as source of truth not changing values outside of code.
* Only supported if backend supports it already)
* @default false
*/
updateMissing?: boolean;
/**
* @default 'fallback'
*/
saveMissingTo?: 'current' | 'all' | 'fallback';
/**
* Used to not fallback to the key as default value, when using saveMissing functionality.
* i.e. when using with i18next-http-backend this will result in having a key with an empty string value.
* @default false
*/
missingKeyNoValueFallbackToKey?: boolean;
/**
* Used for custom missing key handling (needs saveMissing set to true!)
* @default false
*/
missingKeyHandler?:
| false
| ((
lngs: readonly string[],
ns: string,
key: string,
fallbackValue: string,
updateMissing: boolean,
options: any,
) => void);
/**
* Receives a key that was not found in `t()` and returns a value, that will be returned by `t()`
* @default noop
*/
parseMissingKeyHandler?(key: string, defaultValue?: string, options?: any): any;
/**
* Appends namespace to missing key
* @default false
*/
appendNamespaceToMissingKey?: boolean;
/**
* Gets called in case a interpolation value is undefined. This method will not be called if the value is empty string or null
* @default noop
*/
missingInterpolationHandler?: (text: string, value: any, options: InitOptions) => any;
/**
* Will use 'plural' as suffix for languages only having 1 plural form, setting it to false will suffix all with numbers
* @default true
*/
simplifyPluralSuffix?: boolean;
/**
* String or array of postProcessors to apply per default
* @default false
*/
postProcess?: false | string | readonly string[];
/**
* passthrough the resolved object including 'usedNS', 'usedLang' etc into options object of postprocessors as 'i18nResolved' property
* @default false
*/
postProcessPassResolved?: boolean;
/**
* Allows null values as valid translation
* @default false
*/
returnNull?: boolean;
/**
* Allows empty string as valid translation
* @default true
*/
returnEmptyString?: boolean;
/**
* Allows objects as valid translation result
* @default false
*/
returnObjects?: boolean;
/**
* Returns an object that includes information about the used language, namespace, key and value
* @default false
*/
returnDetails?: boolean;
/**
* Gets called if object was passed in as key but returnObjects was set to false
* @default noop
*/
returnedObjectHandler?(key: string, value: string, options: any): void;
/**
* Char, eg. '\n' that arrays will be joined by
* @default false
*/
joinArrays?: false | string;
/**
* Sets defaultValue
* @default args => ({ defaultValue: args[1] })
*/
overloadTranslationOptionHandler?(args: string[]): TOptions;
/**
* @see https://www.i18next.com/translation-function/interpolation
*/
interpolation?: InterpolationOptions;
/**
* Options for react - check documentation of plugin
* @default undefined
*/
react?: ReactOptions;
/**
* Triggers resource loading in init function inside a setTimeout (default async behaviour).
* Set it to false if your backend loads resources sync - that way calling i18next.t after
* init is possible without relaying on the init callback.
* @default true
*/
initAsync?: boolean;
/**
* @deprecated Use initAsync instead.
*/
initImmediate?: boolean;
/**
* Char to separate keys
* @default '.'
*/
keySeparator?: false | string;
/**
* Char to split namespace from key
* @default ':'
*/
nsSeparator?: false | string;
/**
* Char to split plural from key
* @default '_'
*/
pluralSeparator?: string;
/**
* Char to split context from key
* @default '_'
*/
contextSeparator?: string;
/**
* Prefixes the namespace to the returned key when using `cimode`
* @default false
*/
appendNamespaceToCIMode?: boolean;
/**
* Compatibility JSON version
* @warning only `v4` is available and supported by typescript
* @default 'v4'
*/
compatibilityJSON?: 'v4';
/**
* Options for https://github.com/locize/locize-lastused
* @default undefined
*/
locizeLastUsed?: {
/**
* The id of your locize project
*/
projectId: string;
/**
* An api key if you want to send missing keys
*/
apiKey?: string;
/**
* The reference language of your project
* @default 'en'
*/
referenceLng?: string;
/**
* Version
* @default 'latest'
*/
version?: string;
/**
* Debounce interval to send data in milliseconds
* @default 90000
*/
debounceSubmit?: number;
/**
* Hostnames that are allowed to send last used data.
* Please keep those to your local system, staging, test servers (not production)
* @default ['localhost']
*/
allowedHosts?: readonly string[];
};
/**
* Automatically lookup for a flat key if a nested key is not found an vice-versa
* @default true
*/
ignoreJSONStructure?: boolean;
/**
* Limit parallelism of calls to backend
* This is needed to prevent trying to open thousands of
* sockets or file descriptors, which can cause failures
* and actually make the entire process take longer.
* @default 10
*/
maxParallelReads?: number;
/**
* The maximum number of retries to perform.
* Note that retries are only performed when a request has no response
* and throws an error.
* The default value is used if value is set below 0.
* @default 5
*/
maxRetries?: number;
/**
* Set how long to wait, in milliseconds, between retries of failed requests.
* This number is compounded by a factor of 2 for subsequent retry.
* The default value is used if value is set below 1ms.
* @default 350
*/
retryTimeout?: number;
/**
* Initializes the internal formatter for the in-built formats as cached version.
* Can be set to false for this type of issues: https://github.com/i18next/i18next/issues/2227
* @default true
*/
cacheInBuiltFormats?: boolean;
}
export interface TOptionsBase {
/**
* Default value to return if a translation was not found
*/
defaultValue?: unknown;
/**
* Count value used for plurals
*/
count?: number;
/**
* Ordinal flag for ordinal plurals
*/
ordinal?: boolean;
/**
* Used for contexts (eg. male\female)
*/
context?: unknown;
/**
* Object with vars for interpolation - or put them directly in options
*/
replace?: any;
/**
* Override language to use
*/
lng?: string;
/**
* Override languages to use
*/
lngs?: readonly string[];
/**
* Override language to lookup key if not found see fallbacks for details
*/
fallbackLng?: false | FallbackLng;
/**
* Override namespaces (string or array)
*/
ns?: Namespace;
/**
* Override char to separate keys
*/
keySeparator?: false | string;
/**
* Override char to split namespace from key
*/
nsSeparator?: false | string;
/**
* Accessing an object not a translation string (can be set globally too)
*/
returnObjects?: boolean;
/**
* Returns an object that includes information about the used language, namespace, key and value
*/
returnDetails?: boolean;
/**
* Char, eg. '\n' that arrays will be joined by (can be set globally too)
*/
joinArrays?: string;
/**
* String or array of postProcessors to apply see interval plurals as a sample
*/
postProcess?: string | readonly string[];
/**
* Override interpolation options
*/
interpolation?: InterpolationOptions;
/**
* Optional keyPrefix that will be applied to the key before resolving.
* Accepts a string or a selector function (e.g. `$ => $.deeply.nested`).
* Only supported on the TFunction returned by getFixedT().
*/
keyPrefix?: string | (($: any) => any);
}
export type TOptions<TInterpolationMap extends object = $Dictionary> = TOptionsBase &
TInterpolationMap;
export type FlatNamespace = $PreservedValue<keyof TypeOptions['resources'], string>;
export type Namespace<T = FlatNamespace> = T | readonly T[];
export type DefaultNamespace = TypeOptions['defaultNS'];
+775
View File
@@ -0,0 +1,775 @@
import type {
$OmitArrayKeys,
$PreservedValue,
$Dictionary,
$SpecialObject,
$StringKeyPathToRecord,
$NoInfer,
$Prune,
$Turtles,
} from './helpers.js';
import type {
TypeOptions,
Namespace,
FlatNamespace,
DefaultNamespace,
TOptions,
TOptionsBase,
} from './options.js';
/** @todo consider to replace {} with Record<string, never> */
// Type Options
type _ReturnObjects = TypeOptions['returnObjects'];
type _ReturnEmptyString = TypeOptions['returnEmptyString'];
type _ReturnNull = TypeOptions['returnNull'];
type _KeySeparator = TypeOptions['keySeparator'];
type _NsSeparator = TypeOptions['nsSeparator'];
type _PluralSeparator = TypeOptions['pluralSeparator'];
type _ContextSeparator = TypeOptions['contextSeparator'];
type _FallbackNamespace = TypeOptions['fallbackNS'];
type _Resources = TypeOptions['resources'];
type _CompatibilityJSON = TypeOptions['compatibilityJSON'];
type _InterpolationPrefix = TypeOptions['interpolationPrefix'];
type _InterpolationSuffix = TypeOptions['interpolationSuffix'];
type _UnescapePrefix = TypeOptions['unescapePrefix'];
type _UnescapeSuffix = TypeOptions['unescapeSuffix'];
type _StrictKeyChecks = TypeOptions['strictKeyChecks'];
type _EnableSelector = TypeOptions['enableSelector'];
type _InterpolationFormatTypeMap = TypeOptions['interpolationFormatTypeMap'];
type $IsResourcesDefined = [keyof _Resources] extends [never] ? false : true;
type $ValueIfResourcesDefined<Value, Fallback> = $IsResourcesDefined extends true
? Value
: Fallback;
type $FirstNamespace<Ns extends Namespace> = Ns extends readonly any[] ? Ns[0] : Ns;
type Resources = $ValueIfResourcesDefined<_Resources, $Dictionary<string>>;
type PluralSuffix = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
type WithOrWithoutPlural<Key> = _CompatibilityJSON extends 'v4'
? Key extends `${infer KeyWithoutOrdinalPlural}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`
? KeyWithoutOrdinalPlural | Key
: Key extends `${infer KeyWithoutPlural}${_PluralSeparator}${PluralSuffix}`
? KeyWithoutPlural | Key
: Key
: Key;
type JoinKeys<K1, K2> = `${K1 & string}${_KeySeparator}${K2 & string}`;
type AppendNamespace<Ns, Keys> = `${Ns & string}${_NsSeparator}${Keys & string}`;
type TrimSpaces<T extends string, Acc extends string = ''> = T extends `${infer Char}${infer Rest}`
? Char extends ' '
? TrimSpaces<Rest, Acc>
: TrimSpaces<Rest, `${Acc}${Char}`>
: T extends ''
? Acc
: never;
interface Branded<Ns extends Namespace> {
$TFunctionBrand: $IsResourcesDefined extends true
? `${Ns extends readonly any[] ? Ns[0] : Ns}`
: never;
}
/** ****************************************************
* Build all keys and key prefixes based on Resources *
***************************************************** */
type KeysBuilderWithReturnObjects<Res, Key = keyof Res> = Key extends keyof Res
? Res[Key] extends $Dictionary | readonly unknown[]
?
| JoinKeys<Key, WithOrWithoutPlural<keyof $OmitArrayKeys<Res[Key]>>>
| JoinKeys<Key, KeysBuilderWithReturnObjects<Res[Key]>>
: never
: never;
type KeysBuilderWithoutReturnObjects<Res, Key = keyof $OmitArrayKeys<Res>> = Key extends keyof Res
? Res[Key] extends $Dictionary | readonly unknown[]
? JoinKeys<Key, KeysBuilderWithoutReturnObjects<Res[Key]>>
: Key
: never;
type KeysBuilder<Res, WithReturnObjects> = $IsResourcesDefined extends true
? WithReturnObjects extends true
? keyof Res | KeysBuilderWithReturnObjects<Res>
: KeysBuilderWithoutReturnObjects<Res>
: string;
type KeysWithReturnObjects = {
[Ns in FlatNamespace]: WithOrWithoutPlural<KeysBuilder<Resources[Ns], true>>;
};
type KeysWithoutReturnObjects = {
[Ns in FlatNamespace]: WithOrWithoutPlural<KeysBuilder<Resources[Ns], false>>;
};
type ResourceKeys<WithReturnObjects = _ReturnObjects> = WithReturnObjects extends true
? KeysWithReturnObjects
: KeysWithoutReturnObjects;
/** **********************************************************************
* Parse t function keys based on the namespace, options and key prefix *
*********************************************************************** */
export type KeysByTOptions<TOpt extends TOptions> = TOpt['returnObjects'] extends true
? ResourceKeys<true>
: ResourceKeys;
export type NsByTOptions<Ns extends Namespace, TOpt extends TOptions> = TOpt['ns'] extends Namespace
? TOpt['ns']
: Ns;
type ParseKeysByKeyPrefix<Keys, KPrefix> = KPrefix extends string
? Keys extends `${KPrefix}${_KeySeparator}${infer Key}`
? Key
: never
: Keys;
type ParseKeysByNamespaces<Ns extends Namespace, Keys> = Ns extends readonly (infer UnionNsps)[]
? UnionNsps extends keyof Keys
? AppendNamespace<UnionNsps, Keys[UnionNsps]>
: never
: never;
type ParseKeysByFallbackNs<Keys extends $Dictionary> = _FallbackNamespace extends false
? never
: _FallbackNamespace extends (infer UnionFallbackNs extends string)[]
? Keys[UnionFallbackNs]
: Keys[_FallbackNamespace & string];
export type FilterKeysByContext<Keys, Context> = Context extends string
? Keys extends
| `${infer Prefix}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}`
| `${infer Prefix}${_ContextSeparator}${Context}`
? Prefix
: never
: Keys;
export type ParseKeys<
Ns extends Namespace = DefaultNamespace,
TOpt extends TOptions = {},
KPrefix = undefined,
Keys extends $Dictionary = KeysByTOptions<TOpt>,
ActualNS extends Namespace = NsByTOptions<Ns, TOpt>,
Context extends TOpt['context'] = TOpt['context'],
> = $IsResourcesDefined extends true
? FilterKeysByContext<
| ParseKeysByKeyPrefix<Keys[$FirstNamespace<ActualNS>], KPrefix>
| ParseKeysByNamespaces<ActualNS, Keys>
| ParseKeysByFallbackNs<Keys>,
Context
>
: string;
/** *******************************************************
* Parse t function return type and interpolation values *
******************************************************** */
type ParseActualValue<Ret> = Ret extends `${_UnescapePrefix}${infer ActualValue}${_UnescapeSuffix}`
? TrimSpaces<ActualValue>
: Ret;
/** Parses interpolation entries as `[variableName, formatSpecifier | never]` tuples. */
type ParseInterpolationEntries<Ret> =
Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}`
?
| (Value extends `${infer ActualValue},${infer Format}`
? [ParseActualValue<ActualValue>, TrimSpaces<Format>]
: [ParseActualValue<Value>, never])
| ParseInterpolationEntries<Rest>
: never;
/** Extracts just the variable names (kept for backward compat with ParseInterpolationValues usage). */
type ParseInterpolationValues<Ret> = ParseInterpolationEntries<Ret>[0];
/** Built-in i18next formatter name → value type mapping. */
type _BuiltInFormatTypeMap = {
number: number;
currency: number;
datetime: Date;
relativetime: number;
list: readonly string[];
};
/** Resolves the type for a single interpolation entry based on name and format. */
type _ResolveEntryType<Name extends string, Format> = [Format] extends [never]
? Name extends 'count'
? number
: string
: Format extends keyof _InterpolationFormatTypeMap
? _InterpolationFormatTypeMap[Format]
: Format extends keyof _BuiltInFormatTypeMap
? _BuiltInFormatTypeMap[Format]
: string;
/** Local union-to-intersection (not exported from helpers). */
type _UnionToIntersection<T> = (T extends unknown ? (k: T) => void : never) extends (
k: infer I,
) => void
? I
: never;
/** Builds a per-entry typed record from parsed interpolation entries and intersects them. */
type _InterpolationMapFromEntries<E> = _UnionToIntersection<
E extends [infer Name extends string, infer Format]
? $StringKeyPathToRecord<Name, _ResolveEntryType<Name, Format>>
: never
>;
export type InterpolationMap<Ret> = $PreservedValue<
_InterpolationMapFromEntries<ParseInterpolationEntries<Ret>>,
Record<string, unknown>
>;
type ParseTReturnPlural<
Res,
Key,
KeyWithPlural = `${Key & string}${_PluralSeparator}${PluralSuffix}`,
> = Res[(KeyWithPlural | Key) & keyof Res];
type ParseTReturnPluralOrdinal<
Res,
Key,
KeyWithOrdinalPlural = `${Key &
string}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`,
> = Res[(KeyWithOrdinalPlural | Key) & keyof Res];
type ParseTReturnWithFallback<Key, Val> = Val extends ''
? _ReturnEmptyString extends true
? ''
: Key
: Val extends null
? _ReturnNull extends true
? null
: Key
: Val;
type ParseTReturn<Key, Res, TOpt extends TOptions = {}> = ParseTReturnWithFallback<
Key,
Key extends `${infer K1}${_KeySeparator}${infer RestKey}`
? ParseTReturn<RestKey, Res[K1 & keyof Res], TOpt>
: // Process plurals only if count is provided inside options
TOpt['count'] extends number
? TOpt['ordinal'] extends boolean
? ParseTReturnPluralOrdinal<Res, Key>
: ParseTReturnPlural<Res, Key>
: // otherwise access plain key without adding plural and ordinal suffixes
Res extends readonly unknown[]
? Key extends `${infer NKey extends number}`
? Res[NKey]
: never
: Res[Key & keyof Res]
>;
type TReturnOptionalNull = _ReturnNull extends true ? null : never;
type TReturnOptionalObjects<TOpt extends { returnObjects?: unknown }> = _ReturnObjects extends true
? $SpecialObject | string
: TOpt['returnObjects'] extends true
? $SpecialObject
: string;
type DefaultTReturn<TOpt extends { returnObjects?: unknown }> =
| TReturnOptionalObjects<TOpt>
| TReturnOptionalNull;
export type KeyWithContext<Key, TOpt extends TOptions> = TOpt['context'] extends string
? `${Key & string}${_ContextSeparator}${TOpt['context']}`
: Key;
export type ContextOfKey<
Key extends string,
Ns extends Namespace = DefaultNamespace,
TOpt extends TOptions = {},
KPrefix = undefined,
Keys extends $Dictionary = KeysByTOptions<TOpt>,
ActualNS extends Namespace = NsByTOptions<Ns, TOpt>,
ActualKeys =
| ParseKeysByKeyPrefix<Keys[$FirstNamespace<ActualNS>], KPrefix>
| ParseKeysByNamespaces<ActualNS, Keys>
| ParseKeysByFallbackNs<Keys>,
> = $IsResourcesDefined extends true
? Key extends ActualKeys
? string
: ActualKeys extends
| `${Key}${_ContextSeparator}${infer Context}${_PluralSeparator}${PluralSuffix}`
| `${Key}${_ContextSeparator}${infer Context}`
? Context
: never
: string;
// helper that maps the configured fallbackNS value to the matching resources slice
type FallbackResourcesOf<FallbackNS, R> = FallbackNS extends readonly (infer FN)[]
? R[FN & keyof R]
: [FallbackNS] extends [false]
? never
: R[Extract<FallbackNS, keyof R> & keyof R];
/* reuse the parse helpers as top-level aliases (no nested type declarations) */
type _PrimaryParse<
ActualKey,
PrimaryNS extends keyof Resources,
TOpt extends TOptions,
> = ParseTReturn<ActualKey, Resources[PrimaryNS], TOpt>;
type _FallbackParse<ActualKey, FallbackNS, TOpt extends TOptions> = [
FallbackResourcesOf<FallbackNS, Resources>,
] extends [never]
? never
: ParseTReturn<ActualKey, FallbackResourcesOf<FallbackNS, Resources>, TOpt>;
export type TFunctionReturn<
Ns extends Namespace,
Key,
TOpt extends TOptions,
ActualNS extends Namespace = NsByTOptions<Ns, TOpt>,
ActualKey = KeyWithContext<Key, TOpt>,
> = $IsResourcesDefined extends true
? ActualKey extends `${infer Nsp}${_NsSeparator}${infer RestKey}`
? ParseTReturn<RestKey, Resources[Nsp & keyof Resources], TOpt>
: $FirstNamespace<ActualNS> extends infer PrimaryNS
? [PrimaryNS] extends [keyof Resources]
? [_PrimaryParse<ActualKey, PrimaryNS & keyof Resources, TOpt>] extends [never]
? [_FallbackParse<ActualKey, _FallbackNamespace, TOpt>] extends [never]
? DefaultTReturn<TOpt>
: _FallbackParse<ActualKey, _FallbackNamespace, TOpt>
: _PrimaryParse<ActualKey, PrimaryNS & keyof Resources, TOpt>
: never
: never
: DefaultTReturn<TOpt>;
export type TFunctionDetailedResult<T = string, TOpt extends TOptions = {}> = {
/**
* The plain used key
*/
usedKey: string;
/**
* The translation result.
*/
res: T;
/**
* The key with context / plural
*/
exactUsedKey: string;
/**
* The used language for this translation.
*/
usedLng: string;
/**
* The used namespace for this translation.
*/
usedNS: string;
/**
* The parameters used for interpolation.
*/
usedParams: InterpolationMap<T> & { count?: TOpt['count'] };
};
type TFunctionProcessReturnValue<Ret, DefaultValue> = Ret extends string | $SpecialObject | null
? Ret
: [DefaultValue] extends [never]
? Ret
: DefaultValue;
type TFunctionReturnOptionalDetails<Ret, TOpt extends TOptions> = TOpt['returnDetails'] extends true
? TFunctionDetailedResult<Ret, TOpt>
: Ret;
type AppendKeyPrefix<Key, KPrefix> = KPrefix extends string
? `${KPrefix}${_KeySeparator}${Key & string}`
: Key;
/**
* Resolves the effective key prefix by preferring a per-call `keyPrefix` from
* options over the interface-level `KPrefix` (set via getFixedT's 3rd argument).
*/
type EffectiveKPrefix<KPrefix, TOpt> = TOpt extends { keyPrefix: infer OptKP extends string }
? OptKP
: KPrefix;
/** ************************
* T function declaration *
************************* */
interface TFunctionStrict<
Ns extends Namespace = DefaultNamespace,
KPrefix = undefined,
> extends Branded<Ns> {
<
const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
const TOpt extends TOptions,
Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
>(
key: Key | Key[],
options?: TOpt &
InterpolationMap<Ret> & {
context?: Key extends string
? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
: never;
},
): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
<
const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
const TOpt extends TOptions,
Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
>(
key: Key | Key[],
defaultValue: string,
options?: TOpt &
InterpolationMap<Ret> & {
context?: Key extends string
? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
: never;
},
): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
}
interface TFunctionNonStrict<
Ns extends Namespace = DefaultNamespace,
KPrefix = undefined,
> extends Branded<Ns> {
<
const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
const TOpt extends TOptions,
Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
const ActualOptions extends Omit<TOpt, 'context'> &
InterpolationMap<Ret> & {
context?: Key extends string
? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
: never;
} = TOpt &
InterpolationMap<Ret> & {
context?: Key extends string
? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
: never;
},
DefaultValue extends string = never,
>(
...args:
| [key: Key | Key[], options?: ActualOptions]
| [key: string | string[], options: TOpt & $Dictionary & { defaultValue: DefaultValue }]
| [key: string | string[], defaultValue: DefaultValue, options?: TOpt & $Dictionary]
): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, DefaultValue>, TOpt>;
}
type TFunctionSignature<
Ns extends Namespace = DefaultNamespace,
KPrefix = undefined,
> = _EnableSelector extends true | 'optimize'
? TFunctionSelector<Ns, KPrefix, GetSource<Ns, KPrefix>>
: _StrictKeyChecks extends true
? TFunctionStrict<Ns, KPrefix>
: TFunctionNonStrict<Ns, KPrefix>;
export interface TFunction<
Ns extends Namespace = DefaultNamespace,
KPrefix = undefined,
> extends TFunctionSignature<Ns, KPrefix> {}
export type KeyPrefix<Ns extends Namespace> = ResourceKeys<true>[$FirstNamespace<Ns>] | undefined;
/** The raw (unfiltered) resource object for a given namespace. */
export type NsResource<Ns extends Namespace> = Ns extends readonly [keyof Resources, any, ...any]
? Resources[Ns[0]] & PickNamespaces<Resources, Ns[number]>
: Resources[$FirstNamespace<Ns>];
/** A selector function that can be used as `keyPrefix` to scope `t()` to a sub-tree of the resource. */
export type KeyPrefixSelector<Ns extends Namespace> = (src: NsResource<Ns>) => object;
/**
* The type of a selector function accepted by `t()` for a given namespace and optional key prefix.
* Use this instead of `Parameters<TFunction<Ns>>[0]` for a stable, readable type.
*
* @example
* ```ts
* import type { SelectorParam } from 'i18next';
*
* interface CmpProps {
* i18nKey: SelectorParam<'myNamespace'>;
* }
* ```
*/
export type SelectorParam<Ns extends Namespace = DefaultNamespace, KPrefix = undefined> = (
src: Select<GetSource<Ns, KPrefix>, undefined>,
) => string;
/// ////////////// ///
/// ↆ selector ↆ ///
/// ////////////// ///
declare const $PluralBrand: unique symbol;
/** Marks a value as coming from a plural-form key, requiring `count` in the selector options. */
type PluralValue<T extends string> = T & { readonly [$PluralBrand]: typeof $PluralBrand };
declare const $SelectorKeyBrand: unique symbol;
/**
* A branded string produced by {@link keyFromSelector}.
* Can be passed directly to `t()` when the selector API is enabled.
*/
export type SelectorKey = string & { readonly [$SelectorKeyBrand]: typeof $SelectorKeyBrand };
/**
* Type-safe signature for {@link keyFromSelector}.
* Constrains the selector callback against the default namespace's resources
* (same source that `t()` uses for selectors without explicit `ns`).
* When resources are not defined, accepts any selector.
*/
export type KeyFromSelectorFn = <
Ns extends Namespace = DefaultNamespace,
KPrefix extends KeyPrefix<Ns> = undefined,
>(
selector: (src: Select<GetSource<Ns, KPrefix>, undefined>) => any,
opts?: { ns?: Ns; keyPrefix?: KPrefix },
) => SelectorKey;
/** Recursively strips the {@link PluralValue} brand from a type (handles nested objects for `returnObjects`). */
type DeepUnwrapPlural<T> =
T extends PluralValue<infer U>
? U
: T extends readonly any[]
? { [I in keyof T]: DeepUnwrapPlural<T[I]> }
: T extends object
? { [K in keyof T]: DeepUnwrapPlural<T[K]> }
: T;
type NsArg<Ns extends Namespace> = Ns[number] | readonly Ns[number][];
interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Branded<Ns> {
// ── Pre-computed key(s) from keyFromSelector ────────────────────────────────
// Accepts a branded `SelectorKey` (or array of them) produced by `keyFromSelector`.
// Return-type precision is traded for the flexibility of decoupled key creation.
// Placed first so that the more general selector overloads (below) are last —
// TypeScript's `Parameters<>` utility resolves against the last call signature.
<const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>>(
key: SelectorKey | SelectorKey[],
...args: [options?: Opts & $Dictionary]
): DefaultTReturn<Opts>;
// ── Selector(s) with explicit `ns` ───────────────────────────────────────────
<
Target extends ConstrainTarget<Opts>,
const NewNs extends NsArg<Ns> & Namespace,
const Opts extends SelectorOptions<NewNs>,
NewSrc extends GetSource<NewNs, KPrefix>,
>(
selector:
| SelectorFn<NewSrc, ApplyTarget<Target, Opts>, Opts>
| readonly SelectorFn<NewSrc, ApplyTarget<Target, Opts>, Opts>[],
options: Opts & InterpolationMap<Target> & { ns: NewNs },
): SelectorReturn<Target, Opts>;
// ── Array of selectors with default `ns` ─────────────────────────────────────
// Captures the selector tuple as `const Fns` so TypeScript preserves each
// element's exact return type. The union of return types is then extracted
// via a distributive `infer`, which correctly handles mixed PluralValue<T> and
// plain-string callbacks that would otherwise cause TypeScript to "lock in" the
// type from the first element.
<
const Fns extends readonly ((src: Select<Source, Opts['context']>) => string | object)[],
const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
>(
selectors: Fns,
options?: Opts &
InterpolationMap<
DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never>
>,
): SelectorReturn<Fns[number] extends (...args: any[]) => infer R ? R : never, Opts>;
// ── Single selector with context — bypasses count enforcement ────────────────
// When `context` is present in options, `Target` is derived from the
// context-filtered source (third mapped type of FilterKeys), which does NOT
// apply the PluralValue brand. A separate overload avoids the circular
// inference that would otherwise occur when `Opts['context']` sits inside the
// conditional rest tuple of the overload below.
<
Target extends ConstrainTarget<Opts>,
const NewNs extends NsArg<Ns> = Ns[number],
const Opts extends SelectorOptions<NewNs> & { context: string } = SelectorOptions<NewNs> & {
context: string;
},
>(
selector: SelectorFn<Source, ApplyTarget<Target, Opts>, Opts>,
options: Opts & InterpolationMap<Target>,
): SelectorReturn<Target, Opts>;
// ── Single selector with defaultValue — preserves literal type of DV ────────
// `const Opts` loses literal precision for `defaultValue` when inferred
// through a conditional rest tuple (TypeScript limitation). This dedicated
// overload captures `DV` from a regular (non-conditional) parameter position,
// preserving its literal type. Count enforcement for plural keys is achieved
// via a conditional intersection on the options parameter.
<
const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>,
const DV extends string,
const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
>(
selector: Fn,
options: Opts & { defaultValue: DV } & (ReturnType<Fn> extends PluralValue<string>
? { count: number }
: {}) &
InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>,
): SelectorReturn<ReturnType<Fn>, Opts, DV>;
// ── Single selector without context — enforces count for plural keys ──────────
// Uses `const Fn` to capture the selector's exact return type independently of
// `Opts`, breaking the circular dependency between `Target` inference and the
// conditional rest tuple. `ReturnType<Fn>` drives both the plural check and
// the return type; `Opts` is inferred later from the resolved rest args.
<
const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>,
const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
>(
selector: Fn,
...args: ReturnType<Fn> extends PluralValue<string>
? [options: Opts & { count: number } & InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>]
: [options?: Opts & InterpolationMap<ReturnType<Fn>>]
): SelectorReturn<ReturnType<Fn>, Opts>;
}
interface SelectorOptions<Ns = Namespace>
extends Omit<TOptionsBase, 'ns' | 'nsSeparator'>, $Dictionary {
ns?: Ns;
}
type SelectorReturn<
Target,
Opts extends { defaultValue?: unknown; returnObjects?: boolean },
DV = Opts['defaultValue'],
> = $IsResourcesDefined extends true
? TFunctionReturnOptionalDetails<ProcessReturnValue<DeepUnwrapPlural<Target>, DV>, Opts>
: DefaultTReturn<Opts>;
interface SelectorFn<Source, Target, Opts extends SelectorOptions<unknown>> {
(translations: Select<Source, Opts['context']>): Target;
}
type ApplyKeyPrefix<
T extends [any],
KPrefix,
> = KPrefix extends `${infer Head}${_KeySeparator}${infer Tail}`
? ApplyKeyPrefix<[T[0][Head]], Tail>
: T[0][KPrefix & string];
type ApplyTarget<
Target,
Opts extends { returnObjects?: unknown },
> = Opts['returnObjects'] extends true ? unknown : Target;
type ConstrainTarget<Opts extends SelectorOptions<any>> = _ReturnObjects extends true
? unknown
: Opts['returnObjects'] extends true
? unknown
: $IsResourcesDefined extends false
? unknown
: string;
type ProcessReturnValue<Target, DefaultValue> = $Turtles extends Target
? string
: [DefaultValue] extends [never]
? Target
: unknown extends DefaultValue
? Target
: Target | DefaultValue;
type PickNamespaces<T, K extends keyof any> = {
[P in K as P extends keyof T ? P : never]: T[P & keyof T];
};
/** Extracts the sub-tree returned by a selector function used as keyPrefix. */
type SelectorReturnSource<KPrefix, Fallback> = KPrefix extends (...args: any[]) => infer R
? R extends object
? R
: Fallback
: Fallback;
type GetSource<Ns extends Namespace, KPrefix, Res = NsResource<Ns>> = KPrefix extends (
...args: any[]
) => any
? SelectorReturnSource<KPrefix, Res>
: KPrefix extends keyof Res
? Res[KPrefix]
: undefined extends KPrefix
? Res
: ApplyKeyPrefix<[Res], KPrefix>;
type Select<T, Context> = $IsResourcesDefined extends false
? $Turtles
: [_EnableSelector] extends ['optimize']
? T
: FilterKeys<T, Context>;
type _HasContextVariant<T, K extends string, Context> = [
keyof T &
(
| `${K}${_ContextSeparator}${Context & string}`
| `${K}${_ContextSeparator}${Context & string}${_PluralSeparator}${PluralSuffix}`
),
] extends [never]
? false
: true;
/** Checks whether key K has **any** context variant in T (excluding pure plural suffixes). */
type _IsContextualKey<T, K extends string> = [
Exclude<
keyof T & `${K}${_ContextSeparator}${string}`,
| `${K}${_PluralSeparator}${PluralSuffix}`
| `${K}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`
>,
] extends [never]
? false
: true;
type FilterKeys<T, Context> = never | T extends readonly any[]
? { [I in keyof T]: FilterKeys<T[I], Context> }
: $Prune<
{
// Mapped type 1: object-valued keys (recurse) + plain leaf keys (non-plural, non-context)
[K in keyof T as T[K] extends object
? K
: [Context] extends [string]
? K extends
| `${string}${_ContextSeparator}${Context}`
| `${string}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}`
? never // context keys handled by mapped type 3
: K extends `${string}${_PluralSeparator}${PluralSuffix}`
? never // plural keys handled by mapped type 2
: K extends string
? _HasContextVariant<T, K, Context> extends true
? never // context variant exists, drop base key (type 3 handles it)
: _IsContextualKey<T, K> extends true
? never // key has context variants but not for this context
: K // no context variants at all, keep base key
: K
: K extends `${string}${_PluralSeparator}${PluralSuffix}`
? never
: K]: T[K] extends object ? FilterKeys<T[K], Context> : T[K];
} & {
// Mapped type 2: plural collapsing (active regardless of context)
[K in keyof T as T[K] extends object
? never
: [Context] extends [string]
? K extends
| `${string}${_ContextSeparator}${Context}`
| `${string}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}`
? never // context keys handled by mapped type 3
: K extends
| `${infer Prefix}${_PluralSeparator}${PluralSuffix}`
| `${infer Prefix}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`
? Prefix
: never
: K extends
| `${infer Prefix}${_PluralSeparator}${PluralSuffix}`
| `${infer Prefix}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`
? Prefix
: never]: T[K] extends object
? FilterKeys<T[K], Context>
: PluralValue<T[K] & string>;
} & {
// Mapped type 3: context key collapsing
[K in keyof T as T[K] extends object
? never
: [Context] extends [string]
? K extends
| `${infer Prefix}${_ContextSeparator}${Context}`
| `${infer Prefix}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}`
? Prefix
: never
: never]: T[K] extends object ? FilterKeys<T[K], Context> : T[K];
}
>;