@gusnips/react 0.5.1 → 0.5.2

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.
package/README.md CHANGED
@@ -159,6 +159,11 @@ hurry cannot skip.
159
159
  `ErrorBoundary`, an SSE reader split into a platform-free parser and a stream wrapper,
160
160
  `i18nInitOptions`, and `cn`.
161
161
 
162
+ Pass `storageWriter: "app"` to `i18nInitOptions` when your own language control writes the stored
163
+ locale. Leave it out and the detector writes it, which is right until you offer "follow the
164
+ browser": going back to that means clearing the key and detecting again, and a detector that
165
+ writes on every change puts the language it just detected straight back in. One writer per key.
166
+
162
167
  `createAuthStore` is at `@gusnips/react/store` — a zustand store whose `isLoading` starts `false`
163
168
  where there is no window. A session bootstrap can only be in flight in a browser, and `true`
164
169
  during a build is a wait that never ends: it once shipped a spinner as the indexable body of a
package/dist/i18n.d.ts CHANGED
@@ -35,8 +35,22 @@ export interface I18nInitOptions {
35
35
  * Only one donor had this. The other has the same split-origin layout and the same problem.
36
36
  */
37
37
  queryKey?: string;
38
+ /**
39
+ * Who WRITES {@link storageKey}. The detector, by default.
40
+ *
41
+ * Pass `"app"` when your own language control writes it. That is not a preference — it is what
42
+ * a "follow the browser" option costs. Going back to following the browser means clearing the
43
+ * key and calling `changeLanguage(undefined)`, which re-runs detection; a detector that caches
44
+ * then writes the language it just detected straight back into the key it was told to clear.
45
+ * The choice re-pins itself and nothing can reach that state again.
46
+ *
47
+ * Three repos on this stack, three answers: one lets the detector write, one writes it from its
48
+ * own control, and one does both — harmless there only because it offers no "follow the
49
+ * browser" state. So the rule, rather than the default: one writer per key.
50
+ */
51
+ storageWriter?: "detector" | "app";
38
52
  }
39
- export declare function i18nInitOptions({ fallbackLng, supportedLngs, storageKey, queryKey, }: I18nInitOptions): {
53
+ export declare function i18nInitOptions({ fallbackLng, supportedLngs, storageKey, queryKey, storageWriter, }: I18nInitOptions): {
40
54
  fallbackLng: string;
41
55
  supportedLngs: string[];
42
56
  load: "currentOnly";
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,GACT,EAAE,eAAe;;;;;;;;;;;;;;;;EAuBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAmB/E"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;CACpC;AAED,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,EACR,aAA0B,GAC3B,EAAE,eAAe;;;;;;;;;;;;;;;;EAwBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAmB/E"}
package/dist/i18n.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * product from drifting apart on the details below — each of which is a bug somebody already
12
12
  * shipped.
13
13
  */
14
- export function i18nInitOptions({ fallbackLng, supportedLngs, storageKey, queryKey, }) {
14
+ export function i18nInitOptions({ fallbackLng, supportedLngs, storageKey, queryKey, storageWriter = "detector", }) {
15
15
  return {
16
16
  fallbackLng,
17
17
  supportedLngs: [...supportedLngs],
@@ -21,7 +21,8 @@ export function i18nInitOptions({ fallbackLng, supportedLngs, storageKey, queryK
21
21
  order: queryKey
22
22
  ? ["querystring", "localStorage", "navigator"]
23
23
  : ["localStorage", "navigator"],
24
- caches: ["localStorage"],
24
+ // Read from always, written to only by whoever owns it — see `storageWriter`.
25
+ caches: storageWriter === "detector" ? ["localStorage"] : [],
25
26
  ...(queryKey ? { lookupQuerystring: queryKey } : {}),
26
27
  lookupLocalStorage: storageKey,
27
28
  },
package/dist/i18n.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA4BH,MAAM,UAAU,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,GACQ;IAChB,OAAO;QACL,WAAW;QACX,aAAa,EAAE,CAAC,GAAG,aAAa,CAAC;QACjC,IAAI,EAAE,aAAsB;QAC5B,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,yBAAyB;QAChE,SAAS,EAAE;YACT,KAAK,EAAE,QAAQ;gBACb,CAAC,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,WAAW,CAAC;gBAC9C,CAAC,CAAC,CAAC,cAAc,EAAE,WAAW,CAAC;YACjC,MAAM,EAAE,CAAC,cAAc,CAAC;YACxB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,kBAAkB,EAAE,UAAU;SAC/B;QACD,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;QAC7B,gGAAgG;QAChG,4FAA4F;QAC5F,4FAA4F;QAC5F,sFAAsF;QACtF,wFAAwF;QACxF,2FAA2F;QAC3F,8FAA8F;KAC/F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAI,SAAY,EAAE,IAA4B;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,UAAU,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EACvF,GAAG,CACJ,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAE9F,MAAM,IAAI,GAAG,CAAC,KAAc,EAAW,EAAE;QACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YACpC,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,IAAI,CAAC,SAAS,CAAM,CAAC;AAC9B,CAAC"}
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0CH,MAAM,UAAU,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,EACR,aAAa,GAAG,UAAU,GACV;IAChB,OAAO;QACL,WAAW;QACX,aAAa,EAAE,CAAC,GAAG,aAAa,CAAC;QACjC,IAAI,EAAE,aAAsB;QAC5B,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,yBAAyB;QAChE,SAAS,EAAE;YACT,KAAK,EAAE,QAAQ;gBACb,CAAC,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,WAAW,CAAC;gBAC9C,CAAC,CAAC,CAAC,cAAc,EAAE,WAAW,CAAC;YACjC,8EAA8E;YAC9E,MAAM,EAAE,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5D,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,kBAAkB,EAAE,UAAU;SAC/B;QACD,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;QAC7B,gGAAgG;QAChG,4FAA4F;QAC5F,4FAA4F;QAC5F,sFAAsF;QACtF,wFAAwF;QACxF,2FAA2F;QAC3F,8FAA8F;KAC/F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAI,SAAY,EAAE,IAA4B;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,UAAU,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EACvF,GAAG,CACJ,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAE9F,MAAM,IAAI,GAAG,CAAC,KAAc,EAAW,EAAE;QACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YACpC,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,IAAI,CAAC,SAAS,CAAM,CAAC;AAC9B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gusnips/react",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "The headless runtime under a Vite + React SPA: typed fetch client with single-flight refresh, auth store, query defaults, route guards, error boundary, stale-tab recovery, i18n bootstrap.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/i18n.test.ts CHANGED
@@ -83,4 +83,19 @@ describe("i18nInitOptions", () => {
83
83
  expect(detection.order).toEqual(["localStorage", "navigator"]);
84
84
  expect(detection).not.toHaveProperty("lookupQuerystring");
85
85
  });
86
+
87
+ // An app with a "follow the browser" option has to own the key. Getting back to that state
88
+ // means clearing it and re-running detection — and a caching detector writes the language it
89
+ // just detected back into the key it was told to clear, so the choice re-pins itself and the
90
+ // option can never be reached again. The fourth adopter found this; the package had the write
91
+ // hardcoded on.
92
+ it("leaves the key alone when the app is the one writing it", () => {
93
+ const base = { fallbackLng: "en", supportedLngs: ["en"], storageKey: "app.locale" } as const;
94
+ const app = i18nInitOptions({ ...base, storageWriter: "app" });
95
+
96
+ expect(app.detection.caches).toEqual([]);
97
+ // Still READ: the app writes the key, the detector is what notices it.
98
+ expect(app.detection.lookupLocalStorage).toBe("app.locale");
99
+ expect(i18nInitOptions(base).detection.caches).toEqual(["localStorage"]);
100
+ });
86
101
  });
package/src/i18n.ts CHANGED
@@ -36,6 +36,20 @@ export interface I18nInitOptions {
36
36
  * Only one donor had this. The other has the same split-origin layout and the same problem.
37
37
  */
38
38
  queryKey?: string;
39
+ /**
40
+ * Who WRITES {@link storageKey}. The detector, by default.
41
+ *
42
+ * Pass `"app"` when your own language control writes it. That is not a preference — it is what
43
+ * a "follow the browser" option costs. Going back to following the browser means clearing the
44
+ * key and calling `changeLanguage(undefined)`, which re-runs detection; a detector that caches
45
+ * then writes the language it just detected straight back into the key it was told to clear.
46
+ * The choice re-pins itself and nothing can reach that state again.
47
+ *
48
+ * Three repos on this stack, three answers: one lets the detector write, one writes it from its
49
+ * own control, and one does both — harmless there only because it offers no "follow the
50
+ * browser" state. So the rule, rather than the default: one writer per key.
51
+ */
52
+ storageWriter?: "detector" | "app";
39
53
  }
40
54
 
41
55
  export function i18nInitOptions({
@@ -43,6 +57,7 @@ export function i18nInitOptions({
43
57
  supportedLngs,
44
58
  storageKey,
45
59
  queryKey,
60
+ storageWriter = "detector",
46
61
  }: I18nInitOptions) {
47
62
  return {
48
63
  fallbackLng,
@@ -53,7 +68,8 @@ export function i18nInitOptions({
53
68
  order: queryKey
54
69
  ? ["querystring", "localStorage", "navigator"]
55
70
  : ["localStorage", "navigator"],
56
- caches: ["localStorage"],
71
+ // Read from always, written to only by whoever owns it — see `storageWriter`.
72
+ caches: storageWriter === "detector" ? ["localStorage"] : [],
57
73
  ...(queryKey ? { lookupQuerystring: queryKey } : {}),
58
74
  lookupLocalStorage: storageKey,
59
75
  },