fix(i18n-keys): resolve i18next count and context suffixes - #10
Merged
Merged
Conversation
static-translation-key-exists accepted only the literal key, so every plural dictionary (files_one/files_other) forced hand-rolled keys or a lint disable. The rule now mirrors i18next's resolution: a call with a count option accepts <key>_other (or <key>_ordinal_other with ordinal: true), a string-literal context accepts <key>_<context>, and the two combine. Counted calls without an _other fallback report the new missingPluralKey message naming the expected key; an uncounted call to a plural-only key still fails because i18next would render the raw key.
Merged
Closed
agjs
added a commit
to boringstack-xyz/boringstack
that referenced
this pull request
Sep 14, 2026
@boring-stack-pkg/eslint-plugin-i18n-keys 0.1.3 resolves i18next count, ordinal and context suffixes upstream (boringstack-xyz/eslint-plugins#10), so the Bun patch of the published dist, its patchedDependencies entry and the COPY patches lines in the three Dockerfiles go away. The installed plugin regression in tests/lint-meta keeps guarding the behaviour.
agjs
added a commit
to boringstack-xyz/boringstack
that referenced
this pull request
Sep 14, 2026
* fix: address product setup and agent validation feedback * fix(agent): copy UI dependency patches into the isolated candidate image The isolated evaluation image installs apps/ui from package.json and bun.lock alone. With patchedDependencies present, a frozen install aborts on the missing patch file, which failed the agent verification contract job. Copy the patches directory before installing and assert in a unit test that every patched dependency is copied ahead of the install step. * fix(ui): keep the ESLint cache and invalidate it on dictionary changes Removing --cache made every lint run cold. ESLint keys its result cache on a hash of each file's resolved config, so the config now carries a sha256 digest of every English dictionary in settings; editing a dictionary changes the hash and re-lints every file. A regression runs ESLint twice with --cache against a shrinking dictionary and shows the deleted key is reported with the digest and hidden without it. * fix(ui): drop an unused import and format the ESLint config The cache regression built its digest inside a generated config string, so the createHash import in the test itself was unused; the config edit also missed a Prettier pass. * fix(ui): take the plural-aware i18n lint rule from the released plugin @boring-stack-pkg/eslint-plugin-i18n-keys 0.1.3 resolves i18next count, ordinal and context suffixes upstream (boringstack-xyz/eslint-plugins#10), so the Bun patch of the published dist, its patchedDependencies entry and the COPY patches lines in the three Dockerfiles go away. The installed plugin regression in tests/lint-meta keeps guarding the behaviour. * chore(lint): take three rule fixes from the released plugins react-component-architecture 0.3.1 exempts .tsx files that export nothing from component-folder-structure; audit-log 0.2.0 checks a service's public surface only; drizzle-conventions 0.2.0 accepts column arithmetic such as the atomic increment. Guides describe the accepted shapes and the feedback ledger closes rows 40, 65 and 82 as implemented upstream (boringstack-xyz/eslint-plugins#15). * feat: close the remaining product-feedback rows the template can fix itself - Session in lib: useMe, IMe, isAuthenticatedMe and SESSION_QUERY_KEYS move to src/lib/session so every feature reads the current user without a cross-feature exception; auth consumes them too, the allow-list keeps only accounts -> auth for the MFA settings section. Reference UI templates follow. - Cache generations: bumpGeneration / readGeneration / generationScopedKey give seeds, publishes and migrations a way to drop a namespace at once; regression proves old keys become unreachable. - Guides: relational row typing and numeric/bigint strings (drizzle), portable API schemas (typescript, api-client), cache namespaces, grouped store selectors, session module (state-management, routing). - Dead-key lint message says which template literals it can follow. - Ledger rows 23, 24, 26, 36, 37, 38, 51, 63, 78, 80 record the fix or the upstream rule (boringstack-xyz/eslint-plugins#17). * fix(agent): reference UI needs no auth allow-list entry; Docker errors carry a redacted stderr tail The reference UI generator patched the cross-feature allow-list at the ["dashboard", "auth"] anchor to add projects -> auth; with useMe in @/lib/session the entry is unnecessary and the anchor is gone, which failed the isolated positive control. The isolated Docker helper now appends the last 600 characters of stderr with every argument value redacted, so the next failure of this kind is readable in CI. * chore(lint): enable portable schema types and private declarations from the released plugins elysia 0.2.0 adds portable-schema-types, on for every *.schemas.ts; module-boundaries 0.2.0 adds ignorePrivateDeclarations, on for the UI so private helpers and constants stay next to the hook or component that reads them. Ledger rows 21, 35 and 51 close as implemented upstream.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
static-translation-key-existsaccepted only the literal key, so every plural dictionary (files_one/files_other) forced hand-rolledfilesOne/filesOtherkeys or a lint disable. This surfaced twice while building a product on BoringStack (feedback entries 70 and 73), and boringstack-xyz/boringstack#455 currently works around it with abun patchof the publisheddist. This release replaces that patch.The rule now mirrors i18next's key resolution:
countoption accepts<key>_other(or<key>_ordinal_otherwithordinal: true), the fallback form every language must define;contextoption accepts<key>_<context>;<key>_<context>_other, falling back the way i18next does;_otherfallback reports the newmissingPluralKeymessage naming the expected key;t("files")) still fails, because i18next would render the raw key;contextcannot be resolved statically and falls back to the base key.README documents the accepted forms. Changeset: patch release 0.1.3.
Test plan
pnpm typecheck,pnpm test: 17 rule-tester cases (9 valid, 8 invalid) covering count, ordinal, context, combined, nested keys, plain-key fallback, and the error paths.pnpm build, then ran the builtdist/index.jsthrough BoringStack's real ESLint 10 entry point against the fixture fromapps/ui/tests/lint-meta/i18n-plugin.test.tsin #455: 3 errors on lines 4, 5 and 6, matching the test's expectation.