From 367e586e4e70ea08bd53336440f390f901955b79 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 16:01:02 +0000 Subject: [PATCH 1/2] feat(i18n): publish TranslateFn as the one authority; app-shell and fields re-export it (objectui#8261) The narrowed i18next `t` shape `(key: string, options?: Record) => string` was declared byte-identically in app-shell's writeWarningToast and fields' file-size-guard. Per the maintainer's ruling on objectui#8165 (option A) the one declaration now lives in @object-ui/i18n and is published from its entry; both former declarations re-export it, the sibling toast modules and usePublishAllDrafts import it from there, and the TranslateFn row leaves the one-authority gate's KNOWN_COLLISIONS baseline. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN --- .changeset/8165-translatefn-one-authority.md | 22 +++++--- .changeset/8261-translatefn-i18n-authority.md | 26 ++++++++++ .../src/preview/usePublishAllDrafts.ts | 6 ++- .../src/providers/metadataReadWarningToast.ts | 47 +++++++---------- .../src/providers/saveAdvisoryToast.ts | 51 ++++++++----------- .../src/providers/writeWarningToast.ts | 15 +++++- .../fields/src/widgets/file-size-guard.ts | 9 ++-- packages/i18n/README.md | 16 ++++++ packages/i18n/src/index.ts | 5 ++ packages/i18n/src/translateFn.ts | 30 +++++++++++ ...e-authority-per-exported-name-6273.test.ts | 33 +++++------- 11 files changed, 167 insertions(+), 93 deletions(-) create mode 100644 .changeset/8261-translatefn-i18n-authority.md create mode 100644 packages/i18n/src/translateFn.ts diff --git a/.changeset/8165-translatefn-one-authority.md b/.changeset/8165-translatefn-one-authority.md index a172590962..4eca6a7bde 100644 --- a/.changeset/8165-translatefn-one-authority.md +++ b/.changeset/8165-translatefn-one-authority.md @@ -1,9 +1,19 @@ --- --- -Re-point `saveAdvisoryToast`'s `TranslateFn` at its one authority, -`writeWarningToast` (objectui#8165), and shrink the `KNOWN_COLLISIONS` baseline -in `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` by that -site. Type-level only: the declaration was byte-identical to the one it now -re-exports, the name is on no package's published face, and `export type { X } -from '…'` erases at build — so no package is released by this change. +At this change, re-point `saveAdvisoryToast`'s `TranslateFn` at its one +authority, `writeWarningToast` (objectui#8165), and shrink the +`KNOWN_COLLISIONS` baseline in +`scripts/__tests__/one-authority-per-exported-name-6273.test.ts` by that site. +Type-level only: the declaration was byte-identical to the one it re-exported, +at this change the name was on no package's published face, and +`export type { X } from '…'` erases at build — so no package is released by +this change. + +⚠️ **Dated note, 2026-09-25 — the one authority has since moved to +`@object-ui/i18n` — objectui#8261.** Later in this same release `TranslateFn` is +declared once in `@object-ui/i18n` and published from that package's entry; +app-shell's toast modules and `@object-ui/fields`' `file-size-guard` re-export +it from there, and the `KNOWN_COLLISIONS` entry is gone. The rest of this +entry is kept as the reading of this change; the objectui#8261 entry states +what ships. diff --git a/.changeset/8261-translatefn-i18n-authority.md b/.changeset/8261-translatefn-i18n-authority.md new file mode 100644 index 0000000000..eda5ffbcd1 --- /dev/null +++ b/.changeset/8261-translatefn-i18n-authority.md @@ -0,0 +1,26 @@ +--- +'@object-ui/i18n': minor +'@object-ui/app-shell': patch +'@object-ui/fields': patch +--- + +`@object-ui/i18n` now publishes `TranslateFn` — i18next's `t` narrowed to +`(key: string, options?: Record) => string` — as the one +authority for that name (objectui#8261, on the maintainer's ruling on +objectui#8165, option A): + +```ts +import type { TranslateFn } from '@object-ui/i18n'; +``` + +`@object-ui/app-shell` (`writeWarningToast` and its sibling toast modules) and +`@object-ui/fields` (`file-size-guard`) each declared their own copy of this +type. Both copies were byte-identical to the new declaration, and both modules +now re-export it from `@object-ui/i18n` instead of declaring it, so every +existing import of `TranslateFn` from those modules resolves to the same type +it did before — no call site's contract moves. Neither package publishes the +name from its own entry; `@object-ui/i18n` is the one place to import it from. + +The `TranslateFn` entry in the one-authority gate's `KNOWN_COLLISIONS` baseline +(`scripts/__tests__/one-authority-per-exported-name-6273.test.ts`) is removed: +the name now has one declaration. diff --git a/packages/app-shell/src/preview/usePublishAllDrafts.ts b/packages/app-shell/src/preview/usePublishAllDrafts.ts index 282d4fe4c3..6c35d5a612 100644 --- a/packages/app-shell/src/preview/usePublishAllDrafts.ts +++ b/packages/app-shell/src/preview/usePublishAllDrafts.ts @@ -31,8 +31,10 @@ import { publishHealthFromResponse, type PublishHealth } from '@object-ui/plugin import { useMetadataClient } from '../views/metadata-admin/useMetadata.js'; import { emitMetadataRefresh } from '../assistant/assistantBus.js'; import { lintDraftCapabilityReferences } from './capabilityLint.js'; - -type TranslateFn = (key: string, opts?: Record) => string; +// The one authority for the narrowed `t` (objectui#8261) — imported, not +// re-declared. The local copy this replaces differed only in its parameter's +// NAME (`opts`), which TypeScript does not compare, so no caller's contract moves. +import type { TranslateFn } from '@object-ui/i18n'; export interface PublishAllResult { ok: boolean; diff --git a/packages/app-shell/src/providers/metadataReadWarningToast.ts b/packages/app-shell/src/providers/metadataReadWarningToast.ts index a1d4f46a63..a76d1a327f 100644 --- a/packages/app-shell/src/providers/metadataReadWarningToast.ts +++ b/packages/app-shell/src/providers/metadataReadWarningToast.ts @@ -60,40 +60,29 @@ */ import type { MetadataReadWarningEvent } from '@object-ui/data-objectstack'; -import type { TranslateFn } from './writeWarningToast.js'; +import type { TranslateFn } from '@object-ui/i18n'; /** - * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from - * `writeWarningToast`, never re-declared. + * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from its one + * authority in `@object-ui/i18n`, never re-declared. * - * The rest of this module was modelled on its two siblings, and a fourth copy - * of their local `export type TranslateFn = …` came along with the pattern. - * That name already has three declarations (`writeWarningToast`, - * `saveAdvisoryToast`, `fields/src/widgets/file-size-guard`), and the - * objectui#6172 甲/A1 ruling is that every exported name has exactly one - * authority — so a fourth is the one thing this file must not add. - * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` caught it, - * and its remedy is this: `export type { X } from ''` is a - * re-export, not a second declaration, and the gate does not count it. ⛔ The - * baseline it also carries is SHRINK-ONLY and is not an option here. + * The rest of this module was modelled on its two siblings, and when it was + * written (objectui#7741) a further copy of their local + * `export type TranslateFn = …` came along with the pattern. The objectui#6172 + * 甲/A1 ruling is that every exported name has exactly one authority, and + * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` caught it; + * its remedy is this: `export type { X } from ''` is a re-export, + * not a second declaration, and the gate does not count it. ⛔ The baseline it + * also carries is SHRINK-ONLY and is not an option here. * - * Why `writeWarningToast` is the one pointed at, from evidence already in the - * tree rather than a judgement made here: - * - * - `AdapterProvider` — this module's only caller — already imports - * `TranslateFn` from `./writeWarningToast.js` and passes that very value - * into all three emitters, including this one. Pointing here is therefore - * the wiring that already exists, not a new claim about which file owns - * the name. - * - `file-size-guard.ts`'s own declaration names - * `app-shell/src/providers/writeWarningToast` as "the established - * `TranslateFn` pattern" it was copied from. - * - * ⛔ This does NOT resolve the pre-existing three-way collision, and is not an - * attempt to: that predates this card and repairing it belongs to its own. - * This file's obligation is only to stop adding to it. + * The owner was `writeWarningToast` then. objectui#8261 moved the one + * declaration DOWN into `@object-ui/i18n` (the maintainer's ruling on + * objectui#8165, option A) — the only home the `@object-ui/fields` copy could + * re-export from, since `@object-ui/app-shell` depends on `@object-ui/fields` + * and not the other way round. This module points at the owner itself rather + * than at `writeWarningToast`, which is now a re-export too. */ -export type { TranslateFn } from './writeWarningToast.js'; +export type { TranslateFn } from '@object-ui/i18n'; /** * Where the message goes. Structurally satisfied by sonner's `toast`, which is diff --git a/packages/app-shell/src/providers/saveAdvisoryToast.ts b/packages/app-shell/src/providers/saveAdvisoryToast.ts index e7d158be20..00dd41c276 100644 --- a/packages/app-shell/src/providers/saveAdvisoryToast.ts +++ b/packages/app-shell/src/providers/saveAdvisoryToast.ts @@ -38,39 +38,30 @@ */ import type { MetadataSaveAdvisoryEvent } from '@object-ui/data-objectstack'; -import type { TranslateFn } from './writeWarningToast.js'; +import type { TranslateFn } from '@object-ui/i18n'; /** - * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from - * `writeWarningToast`, never re-declared. - * - * This module carried its own byte-identical copy until objectui#8165. The - * objectui#6172 甲/A1 ruling is that every exported name has exactly one - * authority, and - * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` had been - * carrying `TranslateFn` as accepted debt across three files. The remedy that - * gate names is exactly this: `export type { X } from ''` is a - * re-export, not a second declaration, and the gate does not count it. ⛔ Its - * baseline is SHRINK-ONLY, so the entry lost this site in the same PR. - * - * Why `writeWarningToast` is the one pointed at — read off the tree rather - * than decided here: - * - * - `AdapterProvider` is the single caller of all three emitters, and it - * already imports `TranslateFn` from `./writeWarningToast.js` and passes - * that one value into `emitSaveAdvisories` alongside the other two. - * - `metadataReadWarningToast.ts` re-points at it for the same reason, and - * `file-size-guard.ts` names it in its own docblock as the established - * pattern it was copied from. - * - * ⚠️ The THIRD site — `packages/fields/src/widgets/file-size-guard.ts` — is - * deliberately NOT re-pointed and stays on the baseline. `@object-ui/app-shell` - * DEPENDS ON `@object-ui/fields`, so a re-export in that direction is a package - * cycle, and this name is on neither package's published face. Retiring that - * copy means moving the authority DOWN into a package both depend on — a - * different change, and one nobody has ruled on (objectui#8165 reports it). + * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from its one + * authority in `@object-ui/i18n`, never re-declared. + * + * This module carried its own byte-identical copy until objectui#8165, which + * re-pointed it at `writeWarningToast` — then the de facto owner, and the + * module `AdapterProvider` imports it from when it hands one `t` to all three + * emitters. The objectui#6172 甲/A1 ruling is that every exported name has + * exactly one authority, and + * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` is the gate + * that re-derives it: `export type { X } from ''` is a re-export, + * not a second declaration, and the gate does not count it. + * + * objectui#8261 then moved the one declaration DOWN into `@object-ui/i18n` + * (the maintainer's ruling on objectui#8165, option A), because the copy in + * `packages/fields/src/widgets/file-size-guard.ts` could not re-export from + * app-shell — `@object-ui/app-shell` depends on `@object-ui/fields`, so that + * arrow is a package cycle. This module points at the owner itself rather than + * at `writeWarningToast`, which is now a re-export too: a reader following the + * import lands on the declaration in one hop. */ -export type { TranslateFn } from './writeWarningToast.js'; +export type { TranslateFn } from '@object-ui/i18n'; /** * Where the message goes. Structurally satisfied by sonner's `toast`, which is diff --git a/packages/app-shell/src/providers/writeWarningToast.ts b/packages/app-shell/src/providers/writeWarningToast.ts index ac40d66b1e..8553b706b3 100644 --- a/packages/app-shell/src/providers/writeWarningToast.ts +++ b/packages/app-shell/src/providers/writeWarningToast.ts @@ -21,9 +21,20 @@ import type { ObjectStackAdapter, WriteWarningEvent, } from '@object-ui/data-objectstack'; +import type { TranslateFn } from '@object-ui/i18n'; -/** i18next's `t`, narrowed to what this module uses. */ -export type TranslateFn = (key: string, options?: Record) => string; +/** + * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from its one + * authority in `@object-ui/i18n`, never re-declared (objectui#8261). + * + * Its callers keep importing it from here, so no import path moves. This + * module declared it itself until the maintainer's ruling on objectui#8165 + * (option A) moved the one declaration down into `@object-ui/i18n`, the + * package both `@object-ui/app-shell` and `@object-ui/fields` already depend + * on — the copy in `@object-ui/fields` could never re-export from here, + * because app-shell depends on fields and not the other way round. + */ +export type { TranslateFn } from '@object-ui/i18n'; /** Convention-based field-label resolver (`useObjectLabel().fieldLabel`). */ export type FieldLabelFn = (objectName: string, fieldName: string, fallback: string) => string; diff --git a/packages/fields/src/widgets/file-size-guard.ts b/packages/fields/src/widgets/file-size-guard.ts index c7f9363068..ca873659cf 100644 --- a/packages/fields/src/widgets/file-size-guard.ts +++ b/packages/fields/src/widgets/file-size-guard.ts @@ -30,11 +30,14 @@ * post-mortem that restates it lights that census up forever. */ +import type { TranslateFn } from '@object-ui/i18n'; + /** - * Minimal shape of the i18next `t` this module needs, matching the established - * `TranslateFn` pattern in `app-shell/src/providers/writeWarningToast`. + * Minimal shape of the i18next `t` this module needs — RE-EXPORTED from its one + * authority in `@object-ui/i18n`, never re-declared (objectui#8261). The + * sibling widgets that import it from here keep doing so; no import path moves. */ -export type TranslateFn = (key: string, options?: Record) => string; +export type { TranslateFn } from '@object-ui/i18n'; /** Divisor for rendering a byte limit as MB, matching the widgets' size display. */ const BYTES_PER_MB = 1024 * 1024; diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 96455b93d7..d076096f7d 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -128,6 +128,22 @@ const i18n = createI18n({ defaultLanguage: 'de' }); i18n.t('common.cancel'); // "Abbrechen" ``` +### TranslateFn — typing a `t` you are handed + +A helper that receives `t` from its caller, rather than calling a hook itself, +types that parameter with `TranslateFn`: i18next's `t` narrowed to +`(key: string, options?: Record) => string`. This package is +the one place that type is declared — import it rather than declaring a local +copy: + +```ts +import type { TranslateFn } from '@object-ui/i18n'; + +export function saveLabel(t: TranslateFn): string { + return t('common.save'); +} +``` + ### Formatting Utilities Locale-aware formatting functions: diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index 30184f538a..5f2600262d 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -74,6 +74,11 @@ export { createSafeTranslation, useSafeTranslate } from './useSafeTranslation.js // handed to core's `buildChartSeries` as `builtinAggregateLabels`. export { builtinAggregateLabels, type SafeTranslate } from './builtinAggregateLabels.js'; +// objectui#8261 — the ONE authority for i18next's `t` narrowed to +// `(key, options?) => string`. `@object-ui/app-shell` and `@object-ui/fields` +// re-export it from here rather than declaring their own copies. +export type { TranslateFn } from './translateFn.js'; + // Convention-based object/field label i18n export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel.js'; diff --git a/packages/i18n/src/translateFn.ts b/packages/i18n/src/translateFn.ts new file mode 100644 index 0000000000..092691e116 --- /dev/null +++ b/packages/i18n/src/translateFn.ts @@ -0,0 +1,30 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * i18next's `t`, narrowed to the one call shape a plain helper needs: a key, + * optional interpolation / `defaultValue` options, and a string back. + * + * ⭐ This is the ONE authority for the name (objectui#8261, executing the + * maintainer's ruling on objectui#8165, option A). A module that is handed a + * `t` rather than calling a translation hook itself — a toast emitter, a size + * guard, a message builder — types that parameter with this and imports it. + * ⛔ It does not declare its own copy: `@object-ui/app-shell` and + * `@object-ui/fields` each carried a byte-identical one until this change, and + * both now re-export this declaration instead + * (`export type { TranslateFn } from '@object-ui/i18n'`). + * + * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` is what + * re-derives that there is one authority: it reads such a re-export as a + * re-export, and a second `export type TranslateFn = …` anywhere in a + * published package's source as a collision. + * + * Why this package and not `@object-ui/types`: it is a translation-function + * shape, not a schema type — the ruling's own ground. + */ +export type TranslateFn = (key: string, options?: Record) => string; diff --git a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts index 0a8b7933d9..600bed9be0 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -470,27 +470,18 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ ['RecordDetailDrawerProps', ['packages/plugin-dashboard/src/RecordDetailDrawer.tsx', 'packages/plugin-detail/src/RecordDetailDrawer.tsx']], ['SchemaNode', ['packages/sdui-parser/src/types.ts', 'packages/types/src/base.ts']], ['ThemeProviderProps', ['packages/providers/src/types.ts', 'packages/react/src/context/ThemeContext.tsx']], - // `TranslateFn` lost its `packages/app-shell/src/providers/saveAdvisoryToast.ts` - // site in objectui#8165. All three declarations were BYTE-IDENTICAL when that - // was measured (86 bytes each, one sha256 across the three), so there was no - // shape to reconcile; and the authority did not need choosing, because - // `AdapterProvider` is the single caller of all three emitters and already - // imported the type from `./writeWarningToast.js`, passing that one value into - // each of them. `saveAdvisoryToast.ts` now re-exports it, which this gate does - // not count. - // - // ⚠️ The `packages/fields` site STAYS, and that is a measurement rather than an - // oversight. The re-export remedy is dependency-illegal there: - // `@object-ui/app-shell` DEPENDS ON `@object-ui/fields`, so pointing fields at - // app-shell is a package cycle — and `TranslateFn` is on neither package's - // published face (`app-shell/src/index.ts` names it nowhere and has no star - // re-export; `fields/src/index.tsx` stars 50-odd widget modules but not - // `file-size-guard.js`, and none of its three importers re-export the name), so - // there would be nothing to import either. Retiring the last copy means moving - // the authority DOWN into a package both depend on — the `KanbanSchema` route - // above — which publishes a new name from that package and is a decision - // nobody has made. objectui#8165 reports it instead of guessing. - ['TranslateFn', ['packages/app-shell/src/providers/writeWarningToast.ts', 'packages/fields/src/widgets/file-size-guard.ts']], + // `TranslateFn` sat here — i18next's `t` narrowed to `(key, options?) => string`, + // declared BYTE-IDENTICALLY in three files (one sha256 across the three). + // objectui#8165 re-pointed `packages/app-shell/src/providers/saveAdvisoryToast.ts` + // at `writeWarningToast.ts`, but could not do the same for + // `packages/fields/src/widgets/file-size-guard.ts`: `@object-ui/app-shell` + // DEPENDS ON `@object-ui/fields`, so that re-export would be a package cycle. + // The maintainer's ruling on objectui#8165 (option A) moved the ONE declaration + // DOWN into `@object-ui/i18n` — the `KanbanSchema` route above — and + // objectui#8261 landed it: `packages/i18n/src/translateFn.ts` declares it, the + // package entry publishes it, and both former declarations re-export it, which + // this gate does not count. One authority now, so the entry would fail the + // stale-baseline direction. ['UndoRedoState', ['packages/plugin-designer/src/hooks/useUndoRedo.ts', 'packages/types/src/ui-action.ts']], ['UserDataAdapter', ['packages/app-shell/src/context/UserStateAdapters.tsx', 'packages/data-objectstack/src/userState.ts']], // `ValidationFunction` sat here, colliding between From b235ae53d2b8a6c591953d5dda4a34467319f3ce Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 19:43:45 +0000 Subject: [PATCH 2/2] fix(app-shell,i18n): re-point managedByEmptyState's TranslateFn at @object-ui/i18n; make the published prose true (objectui#8261) managedByEmptyState.ts kept a local, unexported, byte-identical copy of the narrowed `t`, which left two published sentences false: the changeset's "the name now has one declaration" and the README's "the one place that type is declared". The copy now imports the one authority; the README says "the one authority for that type" (the same shape still exists under other names), and the changeset names writeWarningToast as the only app-shell module that declared it. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN --- .changeset/8261-translatefn-i18n-authority.md | 3 ++- packages/app-shell/src/utils/managedByEmptyState.ts | 11 ++++------- packages/i18n/README.md | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.changeset/8261-translatefn-i18n-authority.md b/.changeset/8261-translatefn-i18n-authority.md index eda5ffbcd1..1131f56c70 100644 --- a/.changeset/8261-translatefn-i18n-authority.md +++ b/.changeset/8261-translatefn-i18n-authority.md @@ -13,7 +13,8 @@ objectui#8165, option A): import type { TranslateFn } from '@object-ui/i18n'; ``` -`@object-ui/app-shell` (`writeWarningToast` and its sibling toast modules) and +`@object-ui/app-shell` (`writeWarningToast`, which its sibling toast modules +re-export) and `@object-ui/fields` (`file-size-guard`) each declared their own copy of this type. Both copies were byte-identical to the new declaration, and both modules now re-export it from `@object-ui/i18n` instead of declaring it, so every diff --git a/packages/app-shell/src/utils/managedByEmptyState.ts b/packages/app-shell/src/utils/managedByEmptyState.ts index 54e4c1f41d..1127d9c7d3 100644 --- a/packages/app-shell/src/utils/managedByEmptyState.ts +++ b/packages/app-shell/src/utils/managedByEmptyState.ts @@ -22,6 +22,10 @@ * story without repeating themselves verbatim. */ import { resolveEffectiveCrudAffordances, type UserActionsOverride } from '@object-ui/core'; +// The one authority for the narrowed `t` (objectui#8261): a key and optional +// options (including a `defaultValue` used as the English fallback when a +// locale lacks the key) — imported, not re-declared. +import type { TranslateFn } from '@object-ui/i18n'; export interface ManagedByEmptyState { title: string; @@ -29,13 +33,6 @@ export interface ManagedByEmptyState { icon: string; } -/** - * Translator function, structurally compatible with the `t` returned by - * `useObjectTranslation()`. Accepts a key and optional options (including a - * `defaultValue` used as the English fallback when a locale lacks the key). - */ -type TranslateFn = (key: string, options?: Record) => string; - export function resolveManagedByEmptyState( managedBy: string | undefined | null, t: TranslateFn, diff --git a/packages/i18n/README.md b/packages/i18n/README.md index d076096f7d..03d958f0d7 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -133,7 +133,7 @@ i18n.t('common.cancel'); // "Abbrechen" A helper that receives `t` from its caller, rather than calling a hook itself, types that parameter with `TranslateFn`: i18next's `t` narrowed to `(key: string, options?: Record) => string`. This package is -the one place that type is declared — import it rather than declaring a local +the one authority for that type — import it rather than declaring a local copy: ```ts