react-cheminfo 0.53.0 → 0.55.0

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.
Files changed (85) hide show
  1. package/README.md +14 -0
  2. package/lib/chrome/ui/SiteHeader.d.ts +17 -1
  3. package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
  4. package/lib/chrome/ui/SiteHeader.js +9 -2
  5. package/lib/chrome/ui/SiteHeader.js.map +1 -1
  6. package/lib/contact/core/contactUrl.d.ts +35 -0
  7. package/lib/contact/core/contactUrl.d.ts.map +1 -0
  8. package/lib/contact/core/contactUrl.js +33 -0
  9. package/lib/contact/core/contactUrl.js.map +1 -0
  10. package/lib/contact/core/index.d.ts +3 -0
  11. package/lib/contact/core/index.d.ts.map +1 -0
  12. package/lib/contact/core/index.js +2 -0
  13. package/lib/contact/core/index.js.map +1 -0
  14. package/lib/contact/ui/ContactButton.d.ts +41 -0
  15. package/lib/contact/ui/ContactButton.d.ts.map +1 -0
  16. package/lib/contact/ui/ContactButton.js +29 -0
  17. package/lib/contact/ui/ContactButton.js.map +1 -0
  18. package/lib/contact/ui/index.d.ts +3 -0
  19. package/lib/contact/ui/index.d.ts.map +1 -0
  20. package/lib/contact/ui/index.js +2 -0
  21. package/lib/contact/ui/index.js.map +1 -0
  22. package/lib/core.d.ts +1 -0
  23. package/lib/core.d.ts.map +1 -1
  24. package/lib/core.js +1 -0
  25. package/lib/core.js.map +1 -1
  26. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  27. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  28. package/lib/i18n/core/index.d.ts +2 -0
  29. package/lib/i18n/core/index.d.ts.map +1 -1
  30. package/lib/i18n/core/index.js +1 -0
  31. package/lib/i18n/core/index.js.map +1 -1
  32. package/lib/i18n/core/messageCatalog.d.ts +10 -0
  33. package/lib/i18n/core/messageCatalog.d.ts.map +1 -1
  34. package/lib/i18n/core/messageCatalog.js +12 -0
  35. package/lib/i18n/core/messageCatalog.js.map +1 -1
  36. package/lib/i18n/core/translate.d.ts +35 -0
  37. package/lib/i18n/core/translate.d.ts.map +1 -0
  38. package/lib/i18n/core/translate.js +27 -0
  39. package/lib/i18n/core/translate.js.map +1 -0
  40. package/lib/i18n/ui/useT.d.ts +2 -12
  41. package/lib/i18n/ui/useT.d.ts.map +1 -1
  42. package/lib/i18n/ui/useT.js +2 -8
  43. package/lib/i18n/ui/useT.js.map +1 -1
  44. package/lib/i18n/vite/index.d.ts +3 -0
  45. package/lib/i18n/vite/index.d.ts.map +1 -0
  46. package/lib/i18n/vite/index.js +2 -0
  47. package/lib/i18n/vite/index.js.map +1 -0
  48. package/lib/i18n/vite/localeCatalog.d.ts +70 -0
  49. package/lib/i18n/vite/localeCatalog.d.ts.map +1 -0
  50. package/lib/i18n/vite/localeCatalog.js +80 -0
  51. package/lib/i18n/vite/localeCatalog.js.map +1 -0
  52. package/lib/locales/de.json +2 -0
  53. package/lib/locales/en.json +2 -0
  54. package/lib/locales/es.json +2 -0
  55. package/lib/locales/fr.json +2 -0
  56. package/lib/locales/it.json +2 -0
  57. package/lib/ui.d.ts +1 -0
  58. package/lib/ui.d.ts.map +1 -1
  59. package/lib/ui.js +1 -0
  60. package/lib/ui.js.map +1 -1
  61. package/lib/vite.d.ts +1 -0
  62. package/lib/vite.d.ts.map +1 -1
  63. package/lib/vite.js +1 -0
  64. package/lib/vite.js.map +1 -1
  65. package/package.json +1 -1
  66. package/src/chrome/ui/SiteHeader.tsx +35 -3
  67. package/src/contact/core/contactUrl.ts +53 -0
  68. package/src/contact/core/index.ts +2 -0
  69. package/src/contact/ui/ContactButton.tsx +74 -0
  70. package/src/contact/ui/index.ts +2 -0
  71. package/src/core.ts +1 -0
  72. package/src/i18n/core/index.ts +2 -0
  73. package/src/i18n/core/messageCatalog.ts +13 -0
  74. package/src/i18n/core/translate.ts +51 -0
  75. package/src/i18n/ui/useT.ts +6 -29
  76. package/src/i18n/vite/index.ts +5 -0
  77. package/src/i18n/vite/localeCatalog.ts +121 -0
  78. package/src/locales/de.json +2 -0
  79. package/src/locales/en.json +2 -0
  80. package/src/locales/es.json +2 -0
  81. package/src/locales/fr.json +2 -0
  82. package/src/locales/it.json +2 -0
  83. package/src/ui.ts +1 -0
  84. package/src/vite.ts +1 -0
  85. package/styles/chrome.css +21 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The formatter a catalog's text is read through, outside React.
3
+ *
4
+ * `useT` is this function with the page's language and a redraw wired to it;
5
+ * the build and a site's own tests need the same shape without either, so it
6
+ * lives here rather than in the hook. A site never writes its own: four of
7
+ * them had, each with a slightly different fallback.
8
+ */
9
+ import type { Language } from './languages.ts';
10
+ import type { LooseKey, MessageCatalog } from './messageCatalog.ts';
11
+ import type { MessageValues } from './messageValues.ts';
12
+ /**
13
+ * What text is read with.
14
+ *
15
+ * `t(key)` is a key the catalog declares, checked at compile time.
16
+ * `t.or(key, fallback)` is a key built from the data — a site's id, an
17
+ * element's symbol — which the catalog may not have caught up with yet; it
18
+ * reads as the fallback rather than as a raw key.
19
+ */
20
+ export type Translate<TKey extends string> = ((key: LooseKey<TKey>, values?: MessageValues) => string) & {
21
+ or: (key: string, fallback: string, values?: MessageValues) => string;
22
+ };
23
+ /**
24
+ * A catalog's formatter, in one language.
25
+ *
26
+ * In a component this is `useT`, which also redraws when the language or a
27
+ * translator's edit changes it. Here it is for the places a hook cannot go:
28
+ * the build writing a head per address per language, a test asserting on the
29
+ * text a page shows, a label computed in a module helper.
30
+ * @param catalog - The catalog to read, typed by its English keys.
31
+ * @param language - The language to read it in.
32
+ * @returns See {@link Translate}.
33
+ */
34
+ export declare function catalogTranslate<TKey extends string>(catalog: MessageCatalog<TKey>, language: Language): Translate<TKey>;
35
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../../src/i18n/core/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,CAAC,IAAI,SAAS,MAAM,IAAI,CAAC,CAC5C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EACnB,MAAM,CAAC,EAAE,aAAa,KACnB,MAAM,CAAC,GAAG;IACb,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC;CACvE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAClD,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,EAC7B,QAAQ,EAAE,QAAQ,GACjB,SAAS,CAAC,IAAI,CAAC,CAQjB"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The formatter a catalog's text is read through, outside React.
3
+ *
4
+ * `useT` is this function with the page's language and a redraw wired to it;
5
+ * the build and a site's own tests need the same shape without either, so it
6
+ * lives here rather than in the hook. A site never writes its own: four of
7
+ * them had, each with a slightly different fallback.
8
+ */
9
+ /**
10
+ * A catalog's formatter, in one language.
11
+ *
12
+ * In a component this is `useT`, which also redraws when the language or a
13
+ * translator's edit changes it. Here it is for the places a hook cannot go:
14
+ * the build writing a head per address per language, a test asserting on the
15
+ * text a page shows, a label computed in a module helper.
16
+ * @param catalog - The catalog to read, typed by its English keys.
17
+ * @param language - The language to read it in.
18
+ * @returns See {@link Translate}.
19
+ */
20
+ export function catalogTranslate(catalog, language) {
21
+ // `or` is a property of the function itself, so the two are built together
22
+ // rather than assigned onto a value a render may already have read.
23
+ const translate = (key, values) => catalog.translate(key, language, { values });
24
+ translate.or = (key, fallback, values) => catalog.translate(key, language, { fallback, values });
25
+ return translate;
26
+ }
27
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../../src/i18n/core/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqBH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAA6B,EAC7B,QAAkB;IAElB,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,SAAS,GAAG,CAAC,GAAmB,EAAE,MAAsB,EAAE,EAAE,CAChE,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,SAAS,CAAC,EAAE,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAsB,EAAE,EAAE,CACvE,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,17 +1,7 @@
1
1
  import type { ChromeKey } from '../core/chromeCatalog.ts';
2
- import type { Language, LooseKey, MessageValues, TranslateOptions } from '../core/index.ts';
2
+ import type { Language, LooseKey, Translate, TranslateOptions } from '../core/index.ts';
3
3
  import type { MessageCatalog } from '../core/messageCatalog.ts';
4
- /**
5
- * What a component reads its text with.
6
- *
7
- * `t(key)` is a key the catalog declares, checked at compile time.
8
- * `t.or(key, fallback)` is a key built from the data — a site's id, an
9
- * element's symbol — which the catalog may not have caught up with yet; it
10
- * reads as the fallback rather than as a raw key.
11
- */
12
- export type Translate<TKey extends string> = ((key: LooseKey<TKey>, values?: MessageValues) => string) & {
13
- or: (key: string, fallback: string, values?: MessageValues) => string;
14
- };
4
+ export type { Translate } from '../core/index.ts';
15
5
  /**
16
6
  * The formatter of a catalog's text, in the language the page is written in.
17
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useT.d.ts","sourceRoot":"","sources":["../../../src/i18n/ui/useT.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EACV,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,CAAC,IAAI,SAAS,MAAM,IAAI,CAAC,CAC5C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EACnB,MAAM,CAAC,EAAE,aAAa,KACnB,MAAM,CAAC,GAAG;IACb,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC;CACvE,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,MAAM,EACtC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,SAAS,CAAC,IAAI,CAAC,CAKjB;AAeD;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,SAAS,CAAC,SAAS,CAAC,CAEjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAClD,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,EAC7B,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EACnB,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAER"}
1
+ {"version":3,"file":"useT.d.ts","sourceRoot":"","sources":["../../../src/i18n/ui/useT.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EACV,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,MAAM,EACtC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,SAAS,CAAC,IAAI,CAAC,CAKjB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,SAAS,CAAC,SAAS,CAAC,CAEjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAClD,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,EAC7B,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EACnB,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAER"}
@@ -1,4 +1,5 @@
1
1
  import { CHROME_CATALOG } from "../core/chromeCatalog.js";
2
+ import { catalogTranslate } from "../core/index.js";
2
3
  import { useLanguage, useMessagesVersion } from "./useLanguage.js";
3
4
  /**
4
5
  * The formatter of a catalog's text, in the language the page is written in.
@@ -13,14 +14,7 @@ export function useT(catalog) {
13
14
  const language = useLanguage();
14
15
  useMessagesVersion();
15
16
  catalog.prefetch(language);
16
- return translator(catalog, language);
17
- }
18
- // Built outside the hook, because the `or` form is a property of the function
19
- // itself and a render may not write to a value it has just read.
20
- function translator(catalog, language) {
21
- const translate = (key, values) => catalog.translate(key, language, { values });
22
- translate.or = (key, fallback, values) => catalog.translate(key, language, { fallback, values });
23
- return translate;
17
+ return catalogTranslate(catalog, language);
24
18
  }
25
19
  /**
26
20
  * The formatter of this library's own text — the header, the Tools menu, the
@@ -1 +1 @@
1
- {"version":3,"file":"useT.js","sourceRoot":"","sources":["../../../src/i18n/ui/useT.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAS1D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAiBnE;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAClB,OAA6B;IAE7B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,kBAAkB,EAAE,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,iEAAiE;AACjE,SAAS,UAAU,CACjB,OAA6B,EAC7B,QAAkB;IAElB,MAAM,SAAS,GAAG,CAAC,GAAmB,EAAE,MAAsB,EAAE,EAAE,CAChE,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,SAAS,CAAC,EAAE,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAsB,EAAE,EAAE,CACvE,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAA6B,EAC7B,GAAmB,EACnB,QAAkB,EAClB,OAA0B;IAE1B,OAAO,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"useT.js","sourceRoot":"","sources":["../../../src/i18n/ui/useT.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAO1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAMnE;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAClB,OAA6B;IAE7B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,kBAAkB,EAAE,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,OAAO,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAA6B,EAC7B,GAAmB,EACnB,QAAkB,EAClB,OAA0B;IAE1B,OAAO,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export type { LocaleCatalogOptions, LocaleCatalogSource, } from './localeCatalog.ts';
2
+ export { readLocaleCatalog } from './localeCatalog.ts';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/i18n/vite/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { readLocaleCatalog } from "./localeCatalog.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/i18n/vite/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * A site's catalogs as the build reads them: all of them, off disk, at once.
3
+ *
4
+ * The page and the build want opposite things from the same files. A browser
5
+ * fetches one language — the one being read — so `en.json` is part of the
6
+ * bundle and the others are `import()`ed when a visitor asks, which is what
7
+ * keeps a site nobody reads in German from downloading the German. The build
8
+ * writes a head per address per language in one pass, so it wants every
9
+ * language, synchronously, before the first route table is asked for.
10
+ *
11
+ * Reading them rather than fetching them is what makes that possible. The
12
+ * page's loaders are written bare — `import('../locales/fr.json')` with no
13
+ * `with { type: 'json' }` — because the dev server serves a JSON file as a
14
+ * JavaScript module and a browser refuses a chunk it was promised JSON for.
15
+ * Node demands the attribute, so it cannot run those loaders: a build that
16
+ * awaited them had to be async and still only worked by keeping a second,
17
+ * eagerly imported copy of every catalog beside the first. `readFileSync` has
18
+ * no attribute, so there is nothing to work around.
19
+ *
20
+ * The identity comes from the page's own catalog, never re-typed here: pass
21
+ * `SITE_CATALOG.source` and the two provably name the same files.
22
+ */
23
+ import { MessageCatalog } from '../core/index.ts';
24
+ /**
25
+ * Which catalog to read, which is the page catalog's own `source`.
26
+ *
27
+ * `MessageCatalog` publishes it for the translate overlay, and it already
28
+ * carries the three things a reader needs. Hand it over rather than writing
29
+ * them twice: a build that names its own directory is a build that can come to
30
+ * disagree with the page about which files the site is translated in.
31
+ */
32
+ export interface LocaleCatalogSource {
33
+ /**
34
+ * The name the page knows the catalog by, which is the site's host, e.g.
35
+ * `surge.cheminfo.org`. A translator's edit is routed back to the right
36
+ * repository with it, so the build and the page must agree on it.
37
+ */
38
+ id: string;
39
+ /** The GitHub repository the files live in, written `owner/repo`. */
40
+ repository: string;
41
+ /** The directory holding the locale files, relative to the repository root. */
42
+ directory: string;
43
+ }
44
+ /** Where the directory is looked for. */
45
+ export interface LocaleCatalogOptions {
46
+ /**
47
+ * What the source's relative `directory` is resolved against. The default is
48
+ * the working directory, which is the repository root for both callers: a
49
+ * Vite config and a vitest run.
50
+ * @default process.cwd()
51
+ */
52
+ root?: string;
53
+ }
54
+ /**
55
+ * Every language a site is written in, read off disk into one catalog.
56
+ *
57
+ * For the build and for a site's own tests, never for the page: it reads the
58
+ * filesystem, and the translations it holds are tens of kilobytes a first
59
+ * visit must not pay for. Read one language out of it with `catalogTranslate`.
60
+ *
61
+ * A language whose file is absent is left out, which the catalog already reads
62
+ * as English — a half-translated site serves the pages it has rather than
63
+ * failing the build. A missing English file is a misconfiguration and throws:
64
+ * every key and every fallback is declared there.
65
+ * @param source - The page catalog's own `source`. See {@link LocaleCatalogSource}.
66
+ * @param options - See {@link LocaleCatalogOptions}.
67
+ * @returns The catalog, every language it carries already in memory.
68
+ */
69
+ export declare function readLocaleCatalog<TKey extends string = string>(source: LocaleCatalogSource, options?: LocaleCatalogOptions): MessageCatalog<TKey>;
70
+ //# sourceMappingURL=localeCatalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localeCatalog.d.ts","sourceRoot":"","sources":["../../../src/i18n/vite/localeCatalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,OAAO,EAA+B,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5D,MAAM,EAAE,mBAAmB,EAC3B,OAAO,GAAE,oBAAyB,GACjC,cAAc,CAAC,IAAI,CAAC,CAsBtB"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * A site's catalogs as the build reads them: all of them, off disk, at once.
3
+ *
4
+ * The page and the build want opposite things from the same files. A browser
5
+ * fetches one language — the one being read — so `en.json` is part of the
6
+ * bundle and the others are `import()`ed when a visitor asks, which is what
7
+ * keeps a site nobody reads in German from downloading the German. The build
8
+ * writes a head per address per language in one pass, so it wants every
9
+ * language, synchronously, before the first route table is asked for.
10
+ *
11
+ * Reading them rather than fetching them is what makes that possible. The
12
+ * page's loaders are written bare — `import('../locales/fr.json')` with no
13
+ * `with { type: 'json' }` — because the dev server serves a JSON file as a
14
+ * JavaScript module and a browser refuses a chunk it was promised JSON for.
15
+ * Node demands the attribute, so it cannot run those loaders: a build that
16
+ * awaited them had to be async and still only worked by keeping a second,
17
+ * eagerly imported copy of every catalog beside the first. `readFileSync` has
18
+ * no attribute, so there is nothing to work around.
19
+ *
20
+ * The identity comes from the page's own catalog, never re-typed here: pass
21
+ * `SITE_CATALOG.source` and the two provably name the same files.
22
+ */
23
+ import { readFileSync } from 'node:fs';
24
+ import { join, resolve } from 'node:path';
25
+ import { DEFAULT_LANGUAGE, LANGUAGES, MessageCatalog } from "../core/index.js";
26
+ /**
27
+ * Every language a site is written in, read off disk into one catalog.
28
+ *
29
+ * For the build and for a site's own tests, never for the page: it reads the
30
+ * filesystem, and the translations it holds are tens of kilobytes a first
31
+ * visit must not pay for. Read one language out of it with `catalogTranslate`.
32
+ *
33
+ * A language whose file is absent is left out, which the catalog already reads
34
+ * as English — a half-translated site serves the pages it has rather than
35
+ * failing the build. A missing English file is a misconfiguration and throws:
36
+ * every key and every fallback is declared there.
37
+ * @param source - The page catalog's own `source`. See {@link LocaleCatalogSource}.
38
+ * @param options - See {@link LocaleCatalogOptions}.
39
+ * @returns The catalog, every language it carries already in memory.
40
+ */
41
+ export function readLocaleCatalog(source, options = {}) {
42
+ const { id, repository, directory } = source;
43
+ const folder = resolve(options.root ?? process.cwd(), directory);
44
+ const english = readMessages(folder, DEFAULT_LANGUAGE);
45
+ if (english === undefined) {
46
+ throw new Error(`no ${DEFAULT_LANGUAGE}.json in ${folder}: a catalog declares its keys and its fallbacks there`);
47
+ }
48
+ const translations = {};
49
+ for (const language of LANGUAGES) {
50
+ if (language === DEFAULT_LANGUAGE)
51
+ continue;
52
+ const messages = readMessages(folder, language);
53
+ if (messages !== undefined)
54
+ translations[language] = messages;
55
+ }
56
+ return new MessageCatalog({
57
+ id,
58
+ repository,
59
+ directory,
60
+ source: english,
61
+ translations,
62
+ });
63
+ }
64
+ /**
65
+ * One language's messages, read off disk.
66
+ * @param folder - The resolved directory holding the locale files.
67
+ * @param language - The language whose file to read.
68
+ * @returns Its messages, or `undefined` when the site does not carry it.
69
+ */
70
+ function readMessages(folder, language) {
71
+ let text;
72
+ try {
73
+ text = readFileSync(join(folder, `${language}.json`), 'utf8');
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ return JSON.parse(text);
79
+ }
80
+ //# sourceMappingURL=localeCatalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localeCatalog.js","sourceRoot":"","sources":["../../../src/i18n/vite/localeCatalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAkC/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA2B,EAC3B,UAAgC,EAAE;IAElC,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,MAAM,gBAAgB,YAAY,MAAM,uDAAuD,CAChG,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAsD,EAAE,CAAC;IAC3E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,KAAK,gBAAgB;YAAE,SAAS;QAC5C,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;IAChE,CAAC;IACD,OAAO,IAAI,cAAc,CAAO;QAC9B,EAAE;QACF,UAAU;QACV,SAAS;QACT,MAAM,EAAE,OAAO;QACf,YAAY;KACb,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CACnB,MAAc,EACd,QAAkB;IAElB,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,QAAQ,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;AACpD,CAAC"}
@@ -3,6 +3,8 @@
3
3
  "chrome.pages": "Seiten",
4
4
  "chrome.about": "Über",
5
5
  "chrome.aboutTitle": "Was diese Seite ist, worauf sie aufbaut und wie man sie zitiert",
6
+ "chrome.contact": "Kontakt",
7
+ "chrome.contactTitle": "Eine Frage, ein Problem, eine Idee? Schreiben Sie uns",
6
8
  "account.signIn": "Anmelden",
7
9
  "account.signOut": "Abmelden",
8
10
  "account.signedInAs": "Angemeldet als {name}",
@@ -3,6 +3,8 @@
3
3
  "chrome.pages": "Pages",
4
4
  "chrome.about": "About",
5
5
  "chrome.aboutTitle": "What this site is, what it is built on, and how to cite it",
6
+ "chrome.contact": "Contact",
7
+ "chrome.contactTitle": "A question, a problem or an idea? Write to us",
6
8
  "account.signIn": "Sign in",
7
9
  "account.signOut": "Sign out",
8
10
  "account.signedInAs": "Signed in as {name}",
@@ -3,6 +3,8 @@
3
3
  "chrome.pages": "Páginas",
4
4
  "chrome.about": "Acerca de",
5
5
  "chrome.aboutTitle": "Qué es este sitio, en qué se basa y cómo citarlo",
6
+ "chrome.contact": "Contacto",
7
+ "chrome.contactTitle": "¿Una pregunta, un problema, una idea? Escríbanos",
6
8
  "account.signIn": "Iniciar sesión",
7
9
  "account.signOut": "Cerrar sesión",
8
10
  "account.signedInAs": "Sesión iniciada como {name}",
@@ -3,6 +3,8 @@
3
3
  "chrome.pages": "Pages",
4
4
  "chrome.about": "À propos",
5
5
  "chrome.aboutTitle": "Ce qu'est ce site, sur quoi il est bâti et comment le citer",
6
+ "chrome.contact": "Contact",
7
+ "chrome.contactTitle": "Une question, un problème, une idée ? Écrivez-nous",
6
8
  "account.signIn": "Se connecter",
7
9
  "account.signOut": "Se déconnecter",
8
10
  "account.signedInAs": "Connecté comme {name}",
@@ -3,6 +3,8 @@
3
3
  "chrome.pages": "Pagine",
4
4
  "chrome.about": "Informazioni",
5
5
  "chrome.aboutTitle": "Cosa è questo sito, su cosa si basa e come citarlo",
6
+ "chrome.contact": "Contatto",
7
+ "chrome.contactTitle": "Una domanda, un problema, un'idea? Scriveteci",
6
8
  "account.signIn": "Accedi",
7
9
  "account.signOut": "Esci",
8
10
  "account.signedInAs": "Accesso effettuato come {name}",
package/lib/ui.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './capsule/ui/index.ts';
4
4
  export * from './chart/ui/index.ts';
5
5
  export * from './chrome/ui/index.ts';
6
6
  export * from './citation/ui/index.ts';
7
+ export * from './contact/ui/index.ts';
7
8
  export * from './clipboard/ui/index.ts';
8
9
  export * from './color/ui/index.ts';
9
10
  export * from './confirm/ui/index.ts';
package/lib/ui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAKvC,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAKvC,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
package/lib/ui.js CHANGED
@@ -4,6 +4,7 @@ export * from "./capsule/ui/index.js";
4
4
  export * from "./chart/ui/index.js";
5
5
  export * from "./chrome/ui/index.js";
6
6
  export * from "./citation/ui/index.js";
7
+ export * from "./contact/ui/index.js";
7
8
  export * from "./clipboard/ui/index.js";
8
9
  export * from "./color/ui/index.js";
9
10
  export * from "./confirm/ui/index.js";
package/lib/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAMvC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAmC,CAAC;AAEnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAMvC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAmC,CAAC;AAEnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
package/lib/vite.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './build/vite/index.ts';
2
+ export * from './i18n/vite/index.ts';
2
3
  export * from './seo/vite/index.ts';
3
4
  export * from './slides/vite/index.ts';
4
5
  //# sourceMappingURL=vite.d.ts.map
package/lib/vite.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
package/lib/vite.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./build/vite/index.js";
2
+ export * from "./i18n/vite/index.js";
2
3
  export * from "./seo/vite/index.js";
3
4
  export * from "./slides/vite/index.js";
4
5
  //# sourceMappingURL=vite.js.map
package/lib/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-cheminfo",
3
- "version": "0.53.0",
3
+ "version": "0.55.0",
4
4
  "license": "MIT",
5
5
  "description": "React components, hooks and framework-free helpers shared by the cheminfo sites: site chrome, citations, sharing, routing, pedagogy, charts, and the structure, orbital and 3D molecule viewers",
6
6
  "author": "Luc Patiny",
@@ -1,10 +1,13 @@
1
1
  import type { ReactElement, ReactNode } from 'react';
2
2
  import { Fragment } from 'react';
3
3
 
4
+ import { contactFormUrl } from '../../contact/core/contactUrl.ts';
5
+ import { ContactButton } from '../../contact/ui/ContactButton.tsx';
4
6
  import { siteById } from '../../ecosystem/core/lookup.ts';
5
7
  import type { SiteId, SiteRecord } from '../../ecosystem/core/sites.ts';
6
8
  import { Wordmark } from '../../ecosystem/ui/Wordmark.tsx';
7
9
  import { SiteMark } from '../../ecosystem/ui/marks.tsx';
10
+ import { useLanguage } from '../../i18n/ui/useLanguage.ts';
8
11
  import { useChromeT } from '../../i18n/ui/useT.ts';
9
12
 
10
13
  import { NavLink } from './NavLink.tsx';
@@ -39,12 +42,28 @@ export interface SiteHeaderProps {
39
42
  */
40
43
  activeId?: string;
41
44
  /**
42
- * The utilities pushed to the right edge — Cite, Tools, Share, sign in. They
45
+ * The utilities pushed to the right edge — About, Cite, Tools, the language,
46
+ * sign in; Share has a prop of its own, so it always comes last. They
43
47
  * arrive dressed as bar items, so a plain `nav-link` and a `CiteButton` read
44
48
  * alike beside each other.
45
49
  * @default undefined
46
50
  */
47
51
  actions?: ReactNode;
52
+ /**
53
+ * The Share entry, which closes the utilities on every site, after the
54
+ * Contact entry. A site passes its own when it wraps the shared `ShareButton`
55
+ * in a dialog of its own.
56
+ * @default undefined
57
+ */
58
+ share?: ReactNode;
59
+ /**
60
+ * The contact form the Contact entry opens, drawn after the site's utilities
61
+ * and just before Share, with
62
+ * `{site}` and `{page}` where the site and the page are written; `false`
63
+ * leaves the entry out. No entry is drawn while there is no form.
64
+ * @default the family's form in the language of the page (`CONTACT_FORM_URLS`)
65
+ */
66
+ contact?: string | false;
48
67
  /**
49
68
  * Draws one page the site's own way, for a bar whose entries need a tooltip
50
69
  * or a wrapper of their own.
@@ -113,6 +132,8 @@ export function SiteHeader(props: SiteHeaderProps): ReactElement | null {
113
132
  nav,
114
133
  activeId,
115
134
  actions,
135
+ share,
136
+ contact,
116
137
  renderNavItem,
117
138
  embedded = false,
118
139
  homeHref = '/',
@@ -122,6 +143,7 @@ export function SiteHeader(props: SiteHeaderProps): ReactElement | null {
122
143
  pagesLabel,
123
144
  } = props;
124
145
  const t = useChromeT();
146
+ const language = useLanguage();
125
147
 
126
148
  if (embedded) return null;
127
149
 
@@ -129,6 +151,8 @@ export function SiteHeader(props: SiteHeaderProps): ReactElement | null {
129
151
  if (site === undefined) {
130
152
  throw new Error('SiteHeader needs one of its `site` and `siteId` props');
131
153
  }
154
+ const contactForm =
155
+ contact === false ? '' : (contact ?? contactFormUrl(language));
132
156
 
133
157
  return (
134
158
  <header className="app-header no-print">
@@ -169,8 +193,16 @@ export function SiteHeader(props: SiteHeaderProps): ReactElement | null {
169
193
  </div>
170
194
  ) : null}
171
195
  <span className="spacer" />
172
- {actions === undefined ? null : (
173
- <div className="app-header-actions">{actions}</div>
196
+ {actions === undefined &&
197
+ share === undefined &&
198
+ contactForm === '' ? null : (
199
+ <div className="app-header-actions">
200
+ {actions}
201
+ {contactForm === '' ? null : (
202
+ <ContactButton siteId={site.id} formUrl={contactForm} />
203
+ )}
204
+ {share}
205
+ </div>
174
206
  )}
175
207
  </div>
176
208
  </header>
@@ -0,0 +1,53 @@
1
+ import type { Language } from '../../i18n/core/languages.ts';
2
+
3
+ /**
4
+ * Address of the contact form every site of the family opens, one per language the form
5
+ * is written in, with `{site}` and `{page}` where the site and the page are written. A
6
+ * language without a form of its own opens the English one; with no form at all, no
7
+ * Contact entry is drawn.
8
+ */
9
+ export const CONTACT_FORM_URLS: Readonly<Partial<Record<Language, string>>> = {
10
+ en: 'https://docs.google.com/forms/d/e/1FAIpQLSd_1G3tUOEJh_qapmLn_xrybWa9Nu7B57bpYm76o4nwPKPWsg/viewform?usp=pp_url&entry.569380734={site}&entry.2103045774={page}',
11
+ };
12
+
13
+ /** What a contact address is filled with. */
14
+ export interface ContactUrlOptions {
15
+ /** The site the message is about, written as its id. */
16
+ site: string;
17
+ /**
18
+ * The address of the page the reader was on.
19
+ * @default ''
20
+ */
21
+ page?: string;
22
+ }
23
+
24
+ /**
25
+ * The contact form in the language of the page, or the English one when the form is not
26
+ * written in that language.
27
+ * @param language - The language of the page.
28
+ * @param urls - The forms, by language.
29
+ * @returns The address of the form, or an empty string when there is none.
30
+ */
31
+ export function contactFormUrl(
32
+ language: Language,
33
+ urls: Readonly<Partial<Record<Language, string>>> = CONTACT_FORM_URLS,
34
+ ): string {
35
+ return urls[language] ?? urls.en ?? '';
36
+ }
37
+
38
+ /**
39
+ * Fill the address of a contact form with the site and the page, both encoded, so the
40
+ * form opens knowing what the message is about.
41
+ * @param template - Address holding `{site}` and `{page}`.
42
+ * @param options - The site and the page.
43
+ * @returns The address to open.
44
+ */
45
+ export function contactUrl(
46
+ template: string,
47
+ options: ContactUrlOptions,
48
+ ): string {
49
+ const { site, page = '' } = options;
50
+ return template
51
+ .replaceAll('{site}', encodeURIComponent(site))
52
+ .replaceAll('{page}', encodeURIComponent(page));
53
+ }
@@ -0,0 +1,2 @@
1
+ export type { ContactUrlOptions } from './contactUrl.ts';
2
+ export { CONTACT_FORM_URLS, contactFormUrl, contactUrl } from './contactUrl.ts';