@gradio/core 0.16.1 → 0.18.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 (73) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/src/Blocks.svelte +75 -4
  3. package/dist/src/Render.svelte +2 -2
  4. package/dist/src/RenderComponent.svelte +21 -1
  5. package/dist/src/api_docs/ApiDocs.svelte +6 -1
  6. package/dist/src/api_docs/Settings.svelte +74 -1
  7. package/dist/src/api_docs/Settings.svelte.d.ts +3 -0
  8. package/dist/src/api_docs/img/record-stop.svg +1 -0
  9. package/dist/src/api_docs/img/record.svg +1 -0
  10. package/dist/src/gradio_helper.d.ts +1 -11
  11. package/dist/src/gradio_helper.js +3 -19
  12. package/dist/src/i18n.d.ts +12 -5
  13. package/dist/src/i18n.js +93 -12
  14. package/dist/src/init.js +40 -20
  15. package/dist/src/lang/en.json +6 -1
  16. package/dist/src/lang/es.json +2 -1
  17. package/dist/src/lang/fr.json +2 -1
  18. package/dist/src/lang/ja.json +2 -1
  19. package/dist/src/lang/ko.json +2 -1
  20. package/dist/src/lang/lt.json +2 -1
  21. package/dist/src/lang/nb.json +2 -1
  22. package/dist/src/lang/nl.json +2 -1
  23. package/dist/src/lang/pl.json +2 -1
  24. package/dist/src/lang/pt-BR.json +1 -0
  25. package/dist/src/lang/pt.json +2 -1
  26. package/dist/src/lang/ro.json +2 -1
  27. package/dist/src/lang/ru.json +2 -1
  28. package/dist/src/lang/sv.json +2 -1
  29. package/dist/src/lang/ta.json +2 -1
  30. package/dist/src/lang/th.json +2 -1
  31. package/dist/src/lang/tr.json +2 -1
  32. package/dist/src/lang/uk.json +2 -1
  33. package/dist/src/lang/ur.json +2 -1
  34. package/dist/src/lang/uz.json +2 -1
  35. package/dist/src/lang/zh-CN.json +2 -1
  36. package/dist/src/lang/zh-TW.json +2 -1
  37. package/dist/src/screen_recorder.d.ts +16 -0
  38. package/dist/src/screen_recorder.js +255 -0
  39. package/package.json +53 -53
  40. package/src/Blocks.svelte +86 -6
  41. package/src/Render.svelte +2 -2
  42. package/src/RenderComponent.svelte +21 -1
  43. package/src/api_docs/ApiDocs.svelte +7 -1
  44. package/src/api_docs/Settings.svelte +77 -1
  45. package/src/api_docs/img/record-stop.svg +1 -0
  46. package/src/api_docs/img/record.svg +1 -0
  47. package/src/gradio_helper.ts +5 -21
  48. package/src/i18n.test.ts +120 -1
  49. package/src/i18n.ts +126 -24
  50. package/src/init.ts +48 -26
  51. package/src/lang/en.json +6 -1
  52. package/src/lang/es.json +2 -1
  53. package/src/lang/fr.json +2 -1
  54. package/src/lang/ja.json +2 -1
  55. package/src/lang/ko.json +2 -1
  56. package/src/lang/lt.json +2 -1
  57. package/src/lang/nb.json +2 -1
  58. package/src/lang/nl.json +2 -1
  59. package/src/lang/pl.json +2 -1
  60. package/src/lang/pt-BR.json +1 -0
  61. package/src/lang/pt.json +2 -1
  62. package/src/lang/ro.json +2 -1
  63. package/src/lang/ru.json +2 -1
  64. package/src/lang/sv.json +2 -1
  65. package/src/lang/ta.json +2 -1
  66. package/src/lang/th.json +2 -1
  67. package/src/lang/tr.json +2 -1
  68. package/src/lang/uk.json +2 -1
  69. package/src/lang/ur.json +2 -1
  70. package/src/lang/uz.json +2 -1
  71. package/src/lang/zh-CN.json +2 -1
  72. package/src/lang/zh-TW.json +2 -1
  73. package/src/screen_recorder.ts +361 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @gradio/core
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Features
6
+
7
+ - [#11224](https://github.com/gradio-app/gradio/pull/11224) [`834e92c`](https://github.com/gradio-app/gradio/commit/834e92c187f200665c78c344f0b38f5adede807b) - Fix re-rendering with key when setting a value to None. Thanks @aliabid94!
8
+ - [#10832](https://github.com/gradio-app/gradio/pull/10832) [`d457438`](https://github.com/gradio-app/gradio/commit/d4574381bdd12709183c3affe740fada82b8baea) - Screen recording. Thanks @dawoodkhan82!
9
+
10
+ ## 0.17.0
11
+
12
+ ### Features
13
+
14
+ - [#11155](https://github.com/gradio-app/gradio/pull/11155) [`30a1d9e`](https://github.com/gradio-app/gradio/commit/30a1d9e2ac3013d9c844b236410010bce97ffaf5) - Improvements to MCP page. Thanks @abidlabs!
15
+ - [#11047](https://github.com/gradio-app/gradio/pull/11047) [`6d4b8a7`](https://github.com/gradio-app/gradio/commit/6d4b8a7f10daefc9c79aa224635da23fbaeebb76) - Implement custom i18n. Thanks @hannahblair!
16
+
17
+ ### Fixes
18
+
19
+ - [#11206](https://github.com/gradio-app/gradio/pull/11206) [`c196ac2`](https://github.com/gradio-app/gradio/commit/c196ac25b7744e9dcfa788b1ac8edf6551eec1ff) - Render key fixes. Thanks @aliabid94!
20
+
21
+ ### Dependency updates
22
+
23
+ - @gradio/code@0.14.4
24
+ - @gradio/paramviewer@0.7.9
25
+ - @gradio/statustracker@0.10.11
26
+ - @gradio/atoms@0.16.1
27
+ - @gradio/client@1.15.0
28
+ - @gradio/upload@0.16.5
29
+ - @gradio/button@0.5.0
30
+ - @gradio/image@0.22.6
31
+ - @gradio/video@0.14.14
32
+ - @gradio/file@0.12.18
33
+ - @gradio/column@0.2.0
34
+ - @gradio/gallery@0.15.19
35
+ - @gradio/plot@0.9.16
36
+ - @gradio/textbox@0.10.11
37
+ - @gradio/checkbox@0.4.21
38
+
39
+ ## 0.16.1
40
+
41
+ ### Dependency updates
42
+
43
+ - @gradio/tabs@0.4.4
44
+ - @gradio/tabitem@0.4.4
45
+
3
46
  ## 0.16.1
4
47
 
5
48
  ### Fixes
@@ -1,6 +1,7 @@
1
1
  <script>import { tick, onMount } from "svelte";
2
2
  import { _ } from "svelte-i18n";
3
3
  import { Client } from "@gradio/client";
4
+ import { writable } from "svelte/store";
4
5
  import { setupi18n } from "./i18n";
5
6
  import { ApiDocs, ApiRecorder, Settings } from "./api_docs/";
6
7
  import { Toast } from "@gradio/statustracker";
@@ -9,8 +10,9 @@ import { prefix_css } from "./css";
9
10
  import logo from "./images/logo.svg";
10
11
  import api_logo from "./api_docs/img/api-logo.svg";
11
12
  import settings_logo from "./api_docs/img/settings-logo.svg";
13
+ import record_stop from "./api_docs/img/record-stop.svg";
12
14
  import { create_components, AsyncFunction } from "./init";
13
- setupi18n();
15
+ import * as screen_recorder from "./screen_recorder";
14
16
  export let root;
15
17
  export let components;
16
18
  export let layout;
@@ -34,6 +36,7 @@ export let api_prefix = "";
34
36
  export let max_file_size = void 0;
35
37
  export let initial_layout = void 0;
36
38
  export let css = null;
39
+ setupi18n(app.config?.i18n_translations ?? void 0);
37
40
  let {
38
41
  layout: _layout,
39
42
  targets,
@@ -74,6 +77,8 @@ export let search_params;
74
77
  let api_docs_visible = search_params.get("view") === "api" && show_api;
75
78
  let settings_visible = search_params.get("view") === "settings";
76
79
  let api_recorder_visible = search_params.get("view") === "api-recorder" && show_api;
80
+ let allow_zoom = true;
81
+ let allow_video_trim = true;
77
82
  function set_api_docs_visible(visible) {
78
83
  api_recorder_visible = false;
79
84
  api_docs_visible = visible;
@@ -99,6 +104,17 @@ let api_calls = [];
99
104
  export let render_complete = false;
100
105
  async function handle_update(data, fn_index) {
101
106
  const dep = dependencies.find((dep2) => dep2.id === fn_index);
107
+ const input_type = components.find(
108
+ (comp) => comp.id === dep?.inputs[0]
109
+ )?.type;
110
+ if (allow_zoom && dep && input_type !== "dataset") {
111
+ if (dep && dep.inputs && dep.inputs.length > 0 && $is_screen_recording) {
112
+ screen_recorder.zoom(true, dep.inputs, 1);
113
+ }
114
+ if (dep && dep.outputs && dep.outputs.length > 0 && $is_screen_recording) {
115
+ screen_recorder.zoom(false, dep.outputs, 2);
116
+ }
117
+ }
102
118
  if (!dep) {
103
119
  return;
104
120
  }
@@ -285,6 +301,9 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
285
301
  }
286
302
  }
287
303
  async function make_prediction(payload2, streaming = false) {
304
+ if (allow_video_trim) {
305
+ screen_recorder.markRemoveSegmentStart();
306
+ }
288
307
  if (api_recorder_visible) {
289
308
  api_calls = [...api_calls, JSON.parse(JSON.stringify(payload2))];
290
309
  }
@@ -472,6 +491,9 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
472
491
  });
473
492
  }
474
493
  }
494
+ if (allow_video_trim) {
495
+ screen_recorder.markRemoveSegmentEnd();
496
+ }
475
497
  }
476
498
  }
477
499
  function trigger_share(title2, description) {
@@ -612,6 +634,7 @@ function set_status(statuses) {
612
634
  function isCustomEvent(event) {
613
635
  return "detail" in event;
614
636
  }
637
+ let is_screen_recording = writable(false);
615
638
  onMount(() => {
616
639
  document.addEventListener("visibilitychange", function() {
617
640
  if (document.visibilityState === "hidden") {
@@ -621,7 +644,23 @@ onMount(() => {
621
644
  is_mobile_device = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
622
645
  navigator.userAgent
623
646
  );
647
+ screen_recorder.initialize(
648
+ root,
649
+ (title2, message, type) => {
650
+ add_new_message(title2, message, type);
651
+ },
652
+ (isRecording) => {
653
+ $is_screen_recording = isRecording;
654
+ }
655
+ );
624
656
  });
657
+ function screen_recording() {
658
+ if ($is_screen_recording) {
659
+ screen_recorder.stopRecording();
660
+ } else {
661
+ screen_recorder.startRecording();
662
+ }
663
+ }
625
664
  </script>
626
665
 
627
666
  <svelte:head>
@@ -659,7 +698,11 @@ onMount(() => {
659
698
  }}
660
699
  class="show-api"
661
700
  >
662
- {$_("errors.use_via_api")}
701
+ {#if app.config?.mcp_server}
702
+ {$_("errors.use_via_api_or_mcp")}
703
+ {:else}
704
+ {$_("errors.use_via_api")}
705
+ {/if}
663
706
  <img src={api_logo} alt={$_("common.logo")} />
664
707
  </button>
665
708
  <div class="divider show-api-divider">·</div>
@@ -673,6 +716,17 @@ onMount(() => {
673
716
  {$_("common.built_with_gradio")}
674
717
  <img src={logo} alt={$_("common.logo")} />
675
718
  </a>
719
+ <div class="divider" class:hidden={!$is_screen_recording}>·</div>
720
+ <button
721
+ class:hidden={!$is_screen_recording}
722
+ on:click={() => {
723
+ screen_recording();
724
+ }}
725
+ class="record"
726
+ >
727
+ {$_("common.stop_recording")}
728
+ <img src={record_stop} alt={$_("common.stop_recording")} />
729
+ </button>
676
730
  <div class="divider">·</div>
677
731
  <button
678
732
  on:click={() => {
@@ -745,9 +799,14 @@ onMount(() => {
745
799
  />
746
800
  <div class="api-docs-wrap">
747
801
  <Settings
802
+ bind:allow_zoom
803
+ bind:allow_video_trim
748
804
  on:close={(event) => {
749
805
  set_settings_visible(false);
750
806
  }}
807
+ on:start_recording={(event) => {
808
+ screen_recording();
809
+ }}
751
810
  pwa_enabled={app.config.pwa}
752
811
  {root}
753
812
  {space_id}
@@ -787,7 +846,8 @@ onMount(() => {
787
846
  }
788
847
 
789
848
  .show-api,
790
- .settings {
849
+ .settings,
850
+ .record {
791
851
  display: flex;
792
852
  align-items: center;
793
853
  }
@@ -807,13 +867,20 @@ onMount(() => {
807
867
  width: var(--size-4);
808
868
  }
809
869
 
870
+ .record img {
871
+ margin-right: var(--size-1);
872
+ margin-left: var(--size-1);
873
+ width: var(--size-3);
874
+ }
875
+
810
876
  .built-with {
811
877
  display: flex;
812
878
  align-items: center;
813
879
  }
814
880
 
815
881
  .built-with:hover,
816
- .settings:hover {
882
+ .settings:hover,
883
+ .record:hover {
817
884
  color: var(--body-text-color);
818
885
  }
819
886
 
@@ -884,4 +951,8 @@ onMount(() => {
884
951
  .show-api:hover {
885
952
  color: var(--body-text-color);
886
953
  }
954
+
955
+ .hidden {
956
+ display: none;
957
+ }
887
958
  </style>
@@ -1,4 +1,4 @@
1
- <script>import { Gradio, formatter } from "./gradio_helper";
1
+ <script>import { Gradio, reactive_formatter } from "./gradio_helper";
2
2
  import { onMount, createEventDispatcher, setContext } from "svelte";
3
3
  import RenderComponent from "./RenderComponent.svelte";
4
4
  import { load_component } from "virtual:component-loader";
@@ -55,7 +55,7 @@ $:
55
55
  root,
56
56
  autoscroll,
57
57
  max_file_size,
58
- formatter,
58
+ $reactive_formatter,
59
59
  client,
60
60
  load_component
61
61
  );
@@ -1,6 +1,7 @@
1
1
  <svelte:options immutable={true} />
2
2
 
3
- <script>import { bind, binding_callbacks } from "svelte/internal";
3
+ <script>import { translate_if_needed } from "./i18n";
4
+ import { bind, binding_callbacks } from "svelte/internal";
4
5
  export let root;
5
6
  export let component;
6
7
  export let target;
@@ -34,6 +35,25 @@ function wrap(component2) {
34
35
  return ProxiedMyClass;
35
36
  }
36
37
  const _component = wrap(component);
38
+ const supported_props = [
39
+ "description",
40
+ "info",
41
+ "title",
42
+ "placeholder",
43
+ "value",
44
+ "label"
45
+ ];
46
+ function translate_prop(obj) {
47
+ for (const key in obj) {
48
+ if (supported_props.includes(key)) {
49
+ obj[key] = translate_if_needed(obj[key]);
50
+ }
51
+ }
52
+ }
53
+ $:
54
+ translate_prop($$restProps);
55
+ $:
56
+ value = translate_if_needed(value);
37
57
  </script>
38
58
 
39
59
  <!-- {#if visible} -->
@@ -23,6 +23,7 @@ const js_docs = "https://www.gradio.app/guides/getting-started-with-the-js-clien
23
23
  const py_docs = "https://www.gradio.app/guides/getting-started-with-the-python-client";
24
24
  const bash_docs = "https://www.gradio.app/guides/querying-gradio-apps-with-curl";
25
25
  const spaces_docs_suffix = "#connecting-to-a-hugging-face-space";
26
+ const mcp_docs = "https://www.gradio.app/guides/building-mcp-server-with-gradio";
26
27
  let api_count = dependencies.filter(
27
28
  (dependency) => dependency.show_api
28
29
  ).length;
@@ -326,7 +327,11 @@ onMount(() => {
326
327
  </code>
327
328
  </Block>
328
329
  <p>&nbsp;</p>
329
- <p>&nbsp;</p>
330
+ <p>
331
+ <a href={mcp_docs} target="_blank">
332
+ Read more about MCP in the Gradio docs
333
+ </a>
334
+ </p>
330
335
  {:else}
331
336
  This Gradio app can also serve as an MCP server, with an MCP
332
337
  tool corresponding to each API endpoint. To enable this, launch
@@ -4,9 +4,13 @@ export let root;
4
4
  export let space_id;
5
5
  export let pwa_enabled;
6
6
  import { BaseDropdown as Dropdown } from "@gradio/dropdown";
7
+ import { BaseCheckbox as Checkbox } from "@gradio/checkbox";
7
8
  import { language_choices, changeLocale } from "../i18n";
8
9
  import { locale, _ } from "svelte-i18n";
9
10
  import { setupi18n } from "../i18n";
11
+ import record from "./img/record.svg";
12
+ import { createEventDispatcher } from "svelte";
13
+ const dispatch = createEventDispatcher();
10
14
  if (root === "") {
11
15
  root = location.protocol + "//" + location.host + location.pathname;
12
16
  }
@@ -38,6 +42,8 @@ onMount(() => {
38
42
  });
39
43
  let current_locale;
40
44
  let current_theme = "system";
45
+ export let allow_zoom = true;
46
+ export let allow_video_trim = true;
41
47
  locale.subscribe((value) => {
42
48
  if (value) {
43
49
  current_locale = value;
@@ -47,6 +53,12 @@ function handleLanguageChange(e) {
47
53
  const new_locale = e.detail;
48
54
  changeLocale(new_locale);
49
55
  }
56
+ function handleZoomChange(e) {
57
+ allow_zoom = e.detail;
58
+ }
59
+ function handleVideoTrimChange(e) {
60
+ allow_video_trim = e.detail;
61
+ }
50
62
  setupi18n();
51
63
  </script>
52
64
 
@@ -112,6 +124,44 @@ setupi18n();
112
124
  {/if}
113
125
  </p>
114
126
  </div>
127
+ <div class="banner-wrap">
128
+ <h2>{$_("common.screen_studio")} <span class="beta-tag">beta</span></h2>
129
+ <p class="padded">
130
+ Screen Studio allows you to record your screen and generates a video of your
131
+ app with automatically adding zoom in and zoom out effects as well as
132
+ trimming the video to remove the prediction time.
133
+ <br /><br />
134
+ Start recording by clicking the <i>Start Recording</i> button below and then
135
+ sharing the current browser tab of your Gradio demo. Use your app as you
136
+ would normally to generate a prediction.
137
+ <br />
138
+ Stop recording by clicking the <i>Stop Recording</i> button in the footer of
139
+ the demo.
140
+ <br /><br />
141
+ <Checkbox
142
+ label="Include automatic zoom in/out"
143
+ interactive={true}
144
+ value={allow_zoom}
145
+ on:change={handleZoomChange}
146
+ />
147
+ <Checkbox
148
+ label="Include automatic video trimming"
149
+ interactive={true}
150
+ value={allow_video_trim}
151
+ on:change={handleVideoTrimChange}
152
+ />
153
+ </p>
154
+ <button
155
+ class="record-button"
156
+ on:click={() => {
157
+ dispatch("close");
158
+ dispatch("start_recording");
159
+ }}
160
+ >
161
+ <img src={record} alt="Start Recording" />
162
+ Start Recording
163
+ </button>
164
+ </div>
115
165
 
116
166
  <style>
117
167
  .banner-wrap {
@@ -142,7 +192,8 @@ setupi18n();
142
192
  margin-left: var(--size-2);
143
193
  }
144
194
 
145
- .theme-button {
195
+ .theme-button,
196
+ .record-button {
146
197
  display: flex;
147
198
  align-items: center;
148
199
  border: 1px solid var(--border-color-primary);
@@ -154,6 +205,15 @@ setupi18n();
154
205
  cursor: pointer;
155
206
  }
156
207
 
208
+ .record-button img {
209
+ margin-right: var(--size-1);
210
+ margin-left: var(--size-1);
211
+ width: var(--size-3);
212
+ }
213
+ .record-button:hover {
214
+ border-color: red;
215
+ }
216
+
157
217
  .current-theme {
158
218
  border: 1px solid var(--body-text-color-subdued);
159
219
  color: var(--body-text-color);
@@ -173,4 +233,17 @@ setupi18n();
173
233
  all: unset;
174
234
  cursor: pointer;
175
235
  }
236
+
237
+ .beta-tag {
238
+ position: relative;
239
+ top: -5px;
240
+ font-size: var(--text-xs);
241
+ background-color: var(--color-accent);
242
+ color: white;
243
+ padding: 2px 6px;
244
+ border-radius: 10px;
245
+ margin-left: 5px;
246
+ font-weight: normal;
247
+ text-transform: uppercase;
248
+ }
176
249
  </style>
@@ -4,9 +4,12 @@ declare const __propDef: {
4
4
  root: string;
5
5
  space_id: string | null;
6
6
  pwa_enabled: boolean | undefined;
7
+ allow_zoom?: boolean | undefined;
8
+ allow_video_trim?: boolean | undefined;
7
9
  };
8
10
  events: {
9
11
  close: CustomEvent<any>;
12
+ start_recording: CustomEvent<any>;
10
13
  } & {
11
14
  [evt: string]: CustomEvent<any>;
12
15
  };
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>record [#982]</title> <desc>Created with Sketch.</desc> <defs> </defs> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Dribbble-Light-Preview" transform="translate(-380.000000, -3839.000000)" fill="#FF0000"> <g id="icons" transform="translate(56.000000, 160.000000)"> <path d="M338,3689 C338,3691.209 336.209,3693 334,3693 C331.791,3693 330,3691.209 330,3689 C330,3686.791 331.791,3685 334,3685 C336.209,3685 338,3686.791 338,3689 M334,3697 C329.589,3697 326,3693.411 326,3689 C326,3684.589 329.589,3681 334,3681 C338.411,3681 342,3684.589 342,3689 C342,3693.411 338.411,3697 334,3697 M334,3679 C328.477,3679 324,3683.477 324,3689 C324,3694.523 328.477,3699 334,3699 C339.523,3699 344,3694.523 344,3689 C344,3683.477 339.523,3679 334,3679" id="record-[#982]"> </path> </g> </g> </g> </g></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>record [#982]</title> <desc>Created with Sketch.</desc> <defs> </defs> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Dribbble-Light-Preview" transform="translate(-380.000000, -3839.000000)" fill="#808080"> <g id="icons" transform="translate(56.000000, 160.000000)"> <path d="M338,3689 C338,3691.209 336.209,3693 334,3693 C331.791,3693 330,3691.209 330,3689 C330,3686.791 331.791,3685 334,3685 C336.209,3685 338,3686.791 338,3689 M334,3697 C329.589,3697 326,3693.411 326,3689 C326,3684.589 329.589,3681 334,3681 C338.411,3681 342,3684.589 342,3689 C342,3693.411 338.411,3697 334,3697 M334,3679 C328.477,3679 324,3683.477 324,3689 C324,3694.523 328.477,3699 334,3699 C339.523,3699 344,3694.523 344,3689 C344,3683.477 339.523,3679 334,3679" id="record-[#982]"> </path> </g> </g> </g> </g></svg>
@@ -1,14 +1,4 @@
1
1
  export { Gradio } from "@gradio/utils";
2
2
  export type I18nFormatter = typeof formatter;
3
- /**
4
- * i18n formatter with fallback to svelte-i18n's format function.
5
- *
6
- * @param value - The string to translate or format
7
- * @returns The translated string
8
- *
9
- * This formatter attempts translation in the following order:
10
- * 1. Direct translation of the input string
11
- * 2. Checks if input matches any common key names
12
- * 3. Falls back to svelte-i18n's format function
13
- */
14
3
  export declare function formatter(value: string | null | undefined): string;
4
+ export declare const reactive_formatter: import("svelte/store").Readable<typeof formatter>;
@@ -1,25 +1,13 @@
1
- import { format, _ } from "svelte-i18n";
2
- import { get } from "svelte/store";
3
1
  import { all_common_keys } from "./i18n";
2
+ import { _ } from "svelte-i18n";
3
+ import { get, derived } from "svelte/store";
4
4
  export { Gradio } from "@gradio/utils";
5
- /**
6
- * i18n formatter with fallback to svelte-i18n's format function.
7
- *
8
- * @param value - The string to translate or format
9
- * @returns The translated string
10
- *
11
- * This formatter attempts translation in the following order:
12
- * 1. Direct translation of the input string
13
- * 2. Checks if input matches any common key names
14
- * 3. Falls back to svelte-i18n's format function
15
- */
16
5
  export function formatter(value) {
17
6
  if (value == null) {
18
7
  return "";
19
8
  }
20
9
  const string_value = String(value);
21
10
  const translate = get(_);
22
- const initial_formatter = get(format);
23
11
  let direct_translation = translate(string_value);
24
12
  if (direct_translation !== string_value) {
25
13
  return direct_translation;
@@ -35,10 +23,6 @@ export function formatter(value) {
35
23
  break;
36
24
  }
37
25
  }
38
- // fall back to the svelte-i18n formatter to maintain compatibility
39
- const formatted = initial_formatter(string_value);
40
- if (formatted !== string_value) {
41
- return formatted;
42
- }
43
26
  return string_value;
44
27
  }
28
+ export const reactive_formatter = derived(_, () => formatter);
@@ -1,9 +1,16 @@
1
- type LangsRecord = Record<string, {
2
- [key: string]: any;
3
- }>;
1
+ export interface I18nData {
2
+ __type__: "translation_metadata";
3
+ key: string;
4
+ }
5
+ export interface LangsRecord {
6
+ [lang: string]: any;
7
+ }
8
+ export declare function is_translation_metadata(obj: any): obj is I18nData;
9
+ export declare function translate_if_needed(value: any): string;
4
10
  export declare function process_langs(): LangsRecord;
5
11
  export declare const language_choices: [string, string][];
6
12
  export declare let all_common_keys: Set<string>;
7
- export declare function setupi18n(): Promise<void>;
13
+ export declare function setupi18n(custom_translations?: Record<string, Record<string, string>>): Promise<void>;
8
14
  export declare function changeLocale(new_locale: string): void;
9
- export {};
15
+ export declare function get_initial_locale(browser_locale: string | null, available_locales: string[], fallback_locale?: string): string;
16
+ export declare function load_translations(translations: LangsRecord | null | undefined): void;
package/dist/src/i18n.js CHANGED
@@ -1,27 +1,87 @@
1
- import { addMessages, init, getLocaleFromNavigator, locale, _ } from "svelte-i18n";
1
+ import { addMessages, init, getLocaleFromNavigator, locale } from "svelte-i18n";
2
+ import { formatter } from "./gradio_helper";
2
3
  const langs = import.meta.glob("./lang/*.json", {
3
4
  eager: true
4
5
  });
5
- export function process_langs() {
6
- let _langs = {};
7
- for (const lang in langs) {
8
- const code = lang.split("/").pop().split(".").shift();
9
- _langs[code] = langs[lang].default;
6
+ export function is_translation_metadata(obj) {
7
+ console.log(obj);
8
+ const result = obj &&
9
+ typeof obj === "object" &&
10
+ obj.__type__ === "translation_metadata" &&
11
+ typeof obj.key === "string";
12
+ return result;
13
+ }
14
+ // handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
15
+ export function translate_if_needed(value) {
16
+ if (typeof value !== "string") {
17
+ return value;
18
+ }
19
+ const i18n_marker = "__i18n__";
20
+ const marker_index = value.indexOf(i18n_marker);
21
+ if (marker_index === -1) {
22
+ return value;
23
+ }
24
+ try {
25
+ const before_marker = marker_index > 0 ? value.substring(0, marker_index) : "";
26
+ const after_marker_index = marker_index + i18n_marker.length;
27
+ const json_start = value.indexOf("{", after_marker_index);
28
+ let json_end = -1;
29
+ let bracket_count = 0;
30
+ for (let i = json_start; i < value.length; i++) {
31
+ if (value[i] === "{")
32
+ bracket_count++;
33
+ if (value[i] === "}")
34
+ bracket_count--;
35
+ if (bracket_count === 0) {
36
+ json_end = i + 1;
37
+ break;
38
+ }
39
+ }
40
+ if (json_end === -1) {
41
+ console.error("Could not find end of JSON in i18n string");
42
+ return value;
43
+ }
44
+ const metadata_json = value.substring(json_start, json_end);
45
+ const after_json = json_end < value.length ? value.substring(json_end) : "";
46
+ try {
47
+ const metadata = JSON.parse(metadata_json);
48
+ if (metadata && metadata.key) {
49
+ const translated = formatter(metadata.key);
50
+ return before_marker + translated + after_json;
51
+ }
52
+ }
53
+ catch (jsonError) {
54
+ console.error("Error parsing i18n JSON:", jsonError);
55
+ }
56
+ return value;
57
+ }
58
+ catch (e) {
59
+ console.error("Error processing translation:", e);
60
+ return value;
10
61
  }
11
- return _langs;
62
+ }
63
+ export function process_langs() {
64
+ return Object.fromEntries(Object.entries(langs).map(([path, module]) => [
65
+ path.split("/").pop().split(".")[0],
66
+ module.default
67
+ ]));
12
68
  }
13
69
  const processed_langs = process_langs();
14
70
  const available_locales = Object.keys(processed_langs);
15
71
  export const language_choices = Object.entries(processed_langs).map(([code, data]) => [data._name || code, code]);
16
72
  export let all_common_keys = new Set();
17
- for (const lang in processed_langs) {
18
- addMessages(lang, processed_langs[lang]);
19
- }
20
73
  let i18n_initialized = false;
21
- export async function setupi18n() {
22
- if (i18n_initialized) {
74
+ let previous_translations;
75
+ export async function setupi18n(custom_translations) {
76
+ const should_reinitialize = i18n_initialized && custom_translations !== previous_translations;
77
+ if (i18n_initialized && !should_reinitialize) {
23
78
  return;
24
79
  }
80
+ previous_translations = custom_translations;
81
+ load_translations({
82
+ ...processed_langs,
83
+ ...(custom_translations ?? {})
84
+ });
25
85
  const browser_locale = getLocaleFromNavigator();
26
86
  let initial_locale = browser_locale && available_locales.includes(browser_locale)
27
87
  ? browser_locale
@@ -53,3 +113,24 @@ export async function setupi18n() {
53
113
  export function changeLocale(new_locale) {
54
114
  locale.set(new_locale);
55
115
  }
116
+ export function get_initial_locale(browser_locale, available_locales, fallback_locale = "en") {
117
+ if (!browser_locale)
118
+ return fallback_locale;
119
+ if (available_locales.includes(browser_locale)) {
120
+ return browser_locale;
121
+ }
122
+ return fallback_locale;
123
+ }
124
+ export function load_translations(translations) {
125
+ if (!translations) {
126
+ return;
127
+ }
128
+ try {
129
+ for (const lang in translations) {
130
+ addMessages(lang, translations[lang]);
131
+ }
132
+ }
133
+ catch (e) {
134
+ console.error("Error loading translations:", e);
135
+ }
136
+ }