Skip to content

feat(plugin-i18next)!: replace branded keys with native i18next types - #57

Open
RedStar071 wants to merge 3 commits into
mainfrom
feat/i18next-native-types
Open

feat(plugin-i18next)!: replace branded keys with native i18next types#57
RedStar071 wants to merge 3 commits into
mainfrom
feat/i18next-native-types

Conversation

@RedStar071

@RedStar071 RedStar071 commented Aug 29, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

Backport of wolfstar-project/stars-components#30.

Note

This supersedes that PR. @wolfstar/http-framework-i18n — the package it targets — is deprecated
in favour of @wolfstar/plugin-i18next, so upstream #30 can be closed once this lands: the API
changes it makes live here now, and consumers migrate through the plugin's migration guide instead
of upgrading that package.

🧭 Context

stars-components replaced the hand-maintained branded-key helpers (T / FT and the resolve*
functions) in @wolfstar/http-framework-i18n with i18next's own TypeScript support, where keys are
plain strings typed through a CustomTypeOptions augmentation. @wolfstar/plugin-i18next merges
that package with @sapphire/plugin-i18next, so it still shipped the removed API and drifted from
upstream.

📚 Description

Removes the branded-key layer from @wolfstar/plugin-i18next and makes the translator helpers
namespace-aware, matching upstream. Keys are typed by the augmentation generated with
@wolfstar/i18next-type-generator;
without it every helper still accepts plain strings, only untyped.

The generator package itself is not vendored here — it lives in stars-components and is published
to npm, so the README links to it instead. Everything else upstream #30 changed in
http-framework-i18n is covered by this PR, which is what makes that one redundant.

Key changes

Area Before After
Key definition T('commands/ping:success') / FT<{ latency: number }>(...) The key itself, typed by the generated CustomTypeOptions
Key resolution resolveKey(target, key, options) getSupportedLanguageT(target, key, options)
Namespaces getT(locale), always the default namespace getT(locale, namespace?) returning TFunction<Ns>
LocalePrefixKey `commands/${string}:${string}` `${string}${LocaleSeparator}${string}`, with LocaleSeparator from TypeOptions['nsSeparator']

Removed exports: T, FT, resolveKey, resolveUserKey, TypedT, TypedFT, Value, Values,
Difference.

getSupportedLanguageT and getSupportedUserLanguageT keep the call shape resolveKey and
resolveUserKey had: the key and its options go right after the target, and the bound TFunction is
returned only when no key is passed. A non-default namespace is selected through the ns option.

The TFunction augmentation http-framework-i18n shipped is kept for its metadata half — lng,
lngs, ns and keyPrefix, which i18next assigns on every function getFixedT returns but does
not type — so code reading the language off a bound function migrates untouched. Only the branded
T / FT call signatures are gone.

InternationalizationHandler#getT takes an optional namespace and returns a TFunction<Ns> bound to
it. The cached per-language functions are still used when no namespace is passed, so only an explicit
namespace goes through i18next.getFixedT.

The three test suites that relied on the branded keys were rewritten against the translator helpers,
with new cases covering the namespace-bound functions. The README gained a "Typed keys" section for
the generator and a "Migrating off T / FT / resolve*" section mapping every removed export to
its replacement.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation change
  • Workflow / CI change

Pre-flight Checklist

  • I have read the Contributing Guidelines (if applicable)
  • My code follows the code style of this project
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly
  • I added a changeset with pnpm changeset (or pnpm changeset add --empty if no release is needed). See .changeset/README.md.

Confidence Score: 5/5

No blocking failure remains in the changed translation behavior.

The focused runtime checks, package test suite, and TypeScript typecheck completed successfully, and no actionable P0 or P1 finding remains.

T-Rex T-Rex Logs

What T-Rex did

  • I ran focused Vitest tests against direct fixed i18next functions and the updated helper functions, and both paths completed with two assertions passing, confirming that the helper forwarding mirrors the i18next behavior; I also ran the full plugin test suite and the TypeScript typecheck, both of which passed.
  • I validated semantic parity between baseline and helper-forwarding executions, confirming they produced the same assertions on the tested surface, and noted that the temporary Vitest-discovery test source was copied into the package for discovery and then removed after each run, with the authored source uploaded as validation evidence.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "feat(plugin-i18next): declare the metada..." | Re-trigger Greptile

@wolfstarbot wolfstarbot added the packages:plugin-i18next Changes in plugin-i18next label Aug 29, 2026
@RedStar071
RedStar071 marked this pull request as draft August 29, 2026 14:23
@RedStar071
RedStar071 marked this pull request as ready for review August 29, 2026 17:55
Ports wolfstar-project/stars-components#30 to `@wolfstar/plugin-i18next`.

BREAKING CHANGE: `T`, `FT`, `resolveKey`, `resolveUserKey`, `TypedT`,
`TypedFT`, `Value`, `Values` and `Difference` are removed. Keys are now
plain strings typed through the `CustomTypeOptions` augmentation emitted
by `@wolfstar/i18next-type-generator`, and `resolveKey(target, key,
options)` becomes `getSupportedLanguageT(target)(key, options)`.

`LocalePrefixKey` is no longer hardcoded to `commands/<file>:<key>`; it is
now `${string}${LocaleSeparator}${string}`, with the new `LocaleSeparator`
type read from i18next's `TypeOptions["nsSeparator"]`.

`InternationalizationHandler#getT`, `getSupportedLanguageT`,
`getSupportedUserLanguageT` and `fetchT` take an optional namespace and
return a `TFunction<Ns>` bound to it.
@RedStar071
RedStar071 force-pushed the feat/i18next-native-types branch from e87af89 to f8d4b1f Compare August 29, 2026 17:56
@pkg-pr-new

pkg-pr-new Bot commented Aug 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@wolfstar/plugin-api@57
pnpm add https://pkg.pr.new/@wolfstar/plugin-i18next@57
pnpm add https://pkg.pr.new/@wolfstar/plugin-subcommands-advanced@57

commit: adbd664

…pers

`getSupportedLanguageT` and `getSupportedUserLanguageT` take the key and its
options right after the target, matching the call shape `resolveKey` and
`resolveUserKey` had. The bound `TFunction` is still returned when no key is
passed, and a non-default namespace is selected through the `ns` option, so
the namespace parameter these helpers and `fetchT` briefly took is gone;
`InternationalizationHandler#getT` keeps it.
`@wolfstar/http-framework-i18n` declared `lng` and `ns` on `TFunction`, and
dropping that augmentation broke code reading the language off a bound
function even though i18next still assigns it. Declares `lng`, `lngs`, `ns`
and `keyPrefix`, matching what `getFixedT` sets at runtime.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked packages:plugin-i18next Changes in plugin-i18next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants