@open-mercato/shared 0.6.8-develop.7001.1.03608ab252 → 0.6.8-develop.7003.2.24fef49d48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/i18n/context.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { createContext, useContext, useMemo, type
|
|
5
|
-
"mappings": ";AAqES;AApET,SAAS,eAAe,YAAY,
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { createContext, useContext, useMemo, type PropsWithChildren } from 'react'\nimport type { Locale } from './config'\n\nexport type Dict = Record<string, string>\n\nexport type TranslateParams = Record<string, string | number>\n\nexport type TranslateFn = (\n key: string,\n fallbackOrParams?: string | TranslateParams,\n params?: TranslateParams\n) => string\n\nexport type I18nContextValue = {\n locale: Locale\n t: TranslateFn\n /** True when the locale is pinned via `OM_FORCE_LOCALE`; UI should hide switchers. */\n localeLocked: boolean\n}\n\nconst I18N_CONTEXT_KEY = '__openMercatoI18nContext'\n\ntype GlobalI18nContextStore = typeof globalThis & {\n [I18N_CONTEXT_KEY]?: ReturnType<typeof createContext<I18nContextValue | null>>\n}\n\nfunction getI18nContext() {\n const store = globalThis as GlobalI18nContextStore\n if (!store[I18N_CONTEXT_KEY]) {\n store[I18N_CONTEXT_KEY] = createContext<I18nContextValue | null>(null)\n }\n return store[I18N_CONTEXT_KEY]\n}\n\nconst I18nContext = getI18nContext()\n\nfunction format(template: string, params?: TranslateParams) {\n if (!params) return template\n return template.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (_, doubleKey, singleKey) => {\n const key = doubleKey ?? singleKey\n if (!key) return _\n const value = params[key]\n if (value === undefined) {\n return doubleKey ? `{{${key}}}` : `{${key}}`\n }\n return String(value)\n })\n}\n\nexport function I18nProvider({ children, locale, dict, localeLocked = false }: PropsWithChildren<{ locale: Locale; dict: Dict; localeLocked?: boolean }>) {\n const value = useMemo<I18nContextValue>(() => ({\n locale,\n localeLocked,\n t: (key, fallbackOrParams, params) => {\n let fallback: string | undefined\n let resolvedParams: TranslateParams | undefined\n\n if (typeof fallbackOrParams === 'string') {\n fallback = fallbackOrParams\n resolvedParams = params\n } else {\n resolvedParams = fallbackOrParams ?? params\n }\n\n const template = dict[key] ?? fallback ?? key\n return format(template, resolvedParams)\n },\n }), [locale, dict, localeLocked])\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>\n}\n\nexport function useT() {\n const ctx = useContext(I18nContext)\n if (!ctx) throw new Error('useT must be used within I18nProvider')\n return ctx.t\n}\n\n/**\n * Like `useT`, but returns `undefined` instead of throwing when no\n * `I18nProvider` is in scope. Use where a translator is desirable but not\n * guaranteed (e.g. plumbing `t` into side-effect handlers that may run before\n * the provider mounts) \u2014 callers MUST provide a fallback.\n */\nexport function useOptionalT(): TranslateFn | undefined {\n const ctx = useContext(I18nContext)\n return ctx?.t\n}\n\nexport function useLocale() {\n const ctx = useContext(I18nContext)\n if (!ctx) throw new Error('useLocale must be used within I18nProvider')\n return ctx.locale\n}\n\n/**\n * Like `useLocale`, but returns `undefined` instead of throwing when no\n * `I18nProvider` is in scope. Use in shared components that receive their\n * translator as a prop and may render outside a provider (galleries, tests).\n */\nexport function useOptionalLocale(): Locale | undefined {\n const ctx = useContext(I18nContext)\n return ctx?.locale\n}\n\n/**\n * True when the active locale is pinned via `OM_FORCE_LOCALE`. Returns `false`\n * when no provider is in scope so callers can render unconditionally.\n */\nexport function useLocaleLocked() {\n const ctx = useContext(I18nContext)\n return ctx?.localeLocked ?? false\n}\n"],
|
|
5
|
+
"mappings": ";AAqES;AApET,SAAS,eAAe,YAAY,eAAuC;AAoB3E,MAAM,mBAAmB;AAMzB,SAAS,iBAAiB;AACxB,QAAM,QAAQ;AACd,MAAI,CAAC,MAAM,gBAAgB,GAAG;AAC5B,UAAM,gBAAgB,IAAI,cAAuC,IAAI;AAAA,EACvE;AACA,SAAO,MAAM,gBAAgB;AAC/B;AAEA,MAAM,cAAc,eAAe;AAEnC,SAAS,OAAO,UAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,SAAS,QAAQ,4BAA4B,CAAC,GAAG,WAAW,cAAc;AAC/E,UAAM,MAAM,aAAa;AACzB,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,UAAU,QAAW;AACvB,aAAO,YAAY,KAAK,GAAG,OAAO,IAAI,GAAG;AAAA,IAC3C;AACA,WAAO,OAAO,KAAK;AAAA,EACrB,CAAC;AACH;AAEO,SAAS,aAAa,EAAE,UAAU,QAAQ,MAAM,eAAe,MAAM,GAA8E;AACxJ,QAAM,QAAQ,QAA0B,OAAO;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,GAAG,CAAC,KAAK,kBAAkB,WAAW;AACpC,UAAI;AACJ,UAAI;AAEJ,UAAI,OAAO,qBAAqB,UAAU;AACxC,mBAAW;AACX,yBAAiB;AAAA,MACnB,OAAO;AACL,yBAAiB,oBAAoB;AAAA,MACvC;AAEA,YAAM,WAAW,KAAK,GAAG,KAAK,YAAY;AAC1C,aAAO,OAAO,UAAU,cAAc;AAAA,IACxC;AAAA,EACF,IAAI,CAAC,QAAQ,MAAM,YAAY,CAAC;AAChC,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAEO,SAAS,OAAO;AACrB,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uCAAuC;AACjE,SAAO,IAAI;AACb;AAQO,SAAS,eAAwC;AACtD,QAAM,MAAM,WAAW,WAAW;AAClC,SAAO,KAAK;AACd;AAEO,SAAS,YAAY;AAC1B,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4CAA4C;AACtE,SAAO,IAAI;AACb;AAOO,SAAS,oBAAwC;AACtD,QAAM,MAAM,WAAW,WAAW;AAClC,SAAO,KAAK;AACd;AAMO,SAAS,kBAAkB;AAChC,QAAM,MAAM,WAAW,WAAW;AAClC,SAAO,KAAK,gBAAgB;AAC9B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.7003.2.24fef49d48';\nexport const appVersion = APP_VERSION;\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7003.2.24fef49d48",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@mikro-orm/core": "^7.1.8",
|
|
106
106
|
"@mikro-orm/decorators": "^7.1.8",
|
|
107
107
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
108
|
-
"@open-mercato/cache": "0.6.8-develop.
|
|
108
|
+
"@open-mercato/cache": "0.6.8-develop.7003.2.24fef49d48",
|
|
109
109
|
"@types/sanitize-html": "^2.16.1",
|
|
110
110
|
"dotenv": "^17.4.2",
|
|
111
111
|
"pino": "^10.3.1",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { I18nProvider, useT } from '../context'
|
|
4
|
+
|
|
5
|
+
function Greeting() {
|
|
6
|
+
const t = useT()
|
|
7
|
+
return React.createElement('span', null, t('greeting', 'hello'))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('I18nProvider', () => {
|
|
11
|
+
it('accepts children positionally via React.createElement, matching props without a children key', () => {
|
|
12
|
+
const element = React.createElement(
|
|
13
|
+
I18nProvider,
|
|
14
|
+
{ locale: 'en', dict: {} },
|
|
15
|
+
React.createElement(Greeting),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
expect(renderToStaticMarkup(element)).toContain('hello')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('renders with no children at all', () => {
|
|
22
|
+
const element = React.createElement(I18nProvider, { locale: 'en', dict: {} })
|
|
23
|
+
|
|
24
|
+
expect(renderToStaticMarkup(element)).toBe('')
|
|
25
|
+
})
|
|
26
|
+
})
|
package/src/lib/i18n/context.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import { createContext, useContext, useMemo, type
|
|
2
|
+
import { createContext, useContext, useMemo, type PropsWithChildren } from 'react'
|
|
3
3
|
import type { Locale } from './config'
|
|
4
4
|
|
|
5
5
|
export type Dict = Record<string, string>
|
|
@@ -48,7 +48,7 @@ function format(template: string, params?: TranslateParams) {
|
|
|
48
48
|
})
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function I18nProvider({ children, locale, dict, localeLocked = false }: {
|
|
51
|
+
export function I18nProvider({ children, locale, dict, localeLocked = false }: PropsWithChildren<{ locale: Locale; dict: Dict; localeLocked?: boolean }>) {
|
|
52
52
|
const value = useMemo<I18nContextValue>(() => ({
|
|
53
53
|
locale,
|
|
54
54
|
localeLocked,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Compile-time-only regression guard for issue #5155. Never imported at
|
|
2
|
+
// runtime: `yarn typecheck` (`tsc --noEmit`) is the only gate that can catch
|
|
3
|
+
// a regression here, because this repo's Jest transform runs with
|
|
4
|
+
// `isolatedModules: true` and therefore skips type diagnostics — a test in
|
|
5
|
+
// `__tests__/context.test.tsx` would keep passing even if `children` became
|
|
6
|
+
// required again.
|
|
7
|
+
//
|
|
8
|
+
// It sits outside `__tests__` because `packages/shared/tsconfig.json` excludes
|
|
9
|
+
// that directory from `tsc --noEmit`, and it is a `.tsx` rather than a `.ts`
|
|
10
|
+
// because the mutation gate mutates changed `src/lib/**/*.ts` files and runs
|
|
11
|
+
// only their related Jest tests (`enableFindRelatedTests`). A file that exists
|
|
12
|
+
// purely for the type checker has no related tests by construction, so Stryker
|
|
13
|
+
// aborts the run with "No tests were executed"; `.tsx` is already out of that
|
|
14
|
+
// scope by design — see `scripts/stryker/scope.mjs`.
|
|
15
|
+
import * as React from 'react'
|
|
16
|
+
import { I18nProvider } from './context'
|
|
17
|
+
|
|
18
|
+
function acceptsChildrenPositionally() {
|
|
19
|
+
return React.createElement(I18nProvider, { locale: 'en', dict: {} }, <span />)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function acceptsNoChildren() {
|
|
23
|
+
return React.createElement(I18nProvider, { locale: 'en', dict: {} })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function acceptsChildrenAsJsx() {
|
|
27
|
+
return (
|
|
28
|
+
<I18nProvider locale="en" dict={{}}>
|
|
29
|
+
<span />
|
|
30
|
+
</I18nProvider>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void acceptsChildrenPositionally
|
|
35
|
+
void acceptsNoChildren
|
|
36
|
+
void acceptsChildrenAsJsx
|