@gradio/core 1.1.3 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @gradio/core
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Features
6
+
7
+ - [#12839](https://github.com/gradio-app/gradio/pull/12839) [`1c671b3`](https://github.com/gradio-app/gradio/commit/1c671b39830ccf1c87f6cfcb4669e97dfb3a7367) - Hide forms with no elements. Thanks @aliabid94!
8
+
9
+ ### Fixes
10
+
11
+ - [#12875](https://github.com/gradio-app/gradio/pull/12875) [`d0b3422`](https://github.com/gradio-app/gradio/commit/d0b34228d756334d901fc34971959ea422eb55bd) - Fix stop button not switching back to submit button in chat interface. Thanks @freddyaboulton!
12
+ - [#12797](https://github.com/gradio-app/gradio/pull/12797) [`6a0c6ea`](https://github.com/gradio-app/gradio/commit/6a0c6eae53931ec137c0b8379428acc8a7ea27c9) - Refactor translation logic. Thanks @hannahblair!
13
+
14
+ ### Dependency updates
15
+
16
+ - @gradio/utils@0.11.3
17
+ - @gradio/atoms@0.22.0
18
+ - @gradio/statustracker@0.12.4
19
+ - @gradio/tabs@0.5.6
20
+ - @gradio/code@0.17.3
21
+ - @gradio/paramviewer@0.9.4
22
+ - @gradio/upload@0.17.6
23
+ - @gradio/video@0.20.3
24
+ - @gradio/file@0.14.3
25
+ - @gradio/image@0.25.3
26
+ - @gradio/gallery@0.17.1
27
+ - @gradio/plot@0.10.4
28
+ - @gradio/checkbox@0.6.3
29
+ - @gradio/textbox@0.13.4
30
+ - @gradio/dropdown@0.11.4
31
+ - @gradio/audio@0.22.3
32
+
3
33
  ## 1.1.3
4
34
 
5
35
  ### Fixes
@@ -12,7 +12,6 @@
12
12
  LayoutNode
13
13
  } from "./types";
14
14
  // import type { UpdateTransaction } from "./_init";
15
- import { setupi18n } from "./i18n";
16
15
  import type { ThemeMode, Payload } from "./types";
17
16
  import { Toast } from "@gradio/statustracker";
18
17
  import type { ToastMessage } from "@gradio/statustracker";
package/dist/src/_init.js CHANGED
@@ -3,7 +3,7 @@ import { dequal } from "dequal";
3
3
  import { load_component } from "virtual:component-loader";
4
4
  import { create_loading_status_store } from "./stores";
5
5
  import { _ } from "svelte-i18n";
6
- import { i18n_marker } from "./i18n";
6
+ import { has_i18n_marker } from "@gradio/utils";
7
7
  const shared_props = [
8
8
  "elem_id",
9
9
  "elem_classes",
@@ -225,7 +225,7 @@ export function create_components({ initial_layout = undefined } = {
225
225
  const _type = instance.type === "walkthrough" ? "walkthroughstep" : "tabitem";
226
226
  const child_tab_items = tab_items_props.filter((child) => child.type === _type);
227
227
  instance.props.initial_tabs = child_tab_items?.map((child) => ({
228
- label: child.props.label.includes(i18n_marker) ? "" : child.props.label,
228
+ label: has_i18n_marker(child.props.label) ? "" : child.props.label,
229
229
  id: child.props.id,
230
230
  visible: typeof child.props.visible === "boolean" ||
231
231
  child.props.visible === "hidden"
@@ -6,6 +6,7 @@
6
6
  import { BaseCheckbox as Checkbox } from "@gradio/checkbox";
7
7
  import { language_choices, changeLocale } from "../i18n";
8
8
  import { locale, _ } from "svelte-i18n";
9
+ import { get } from "svelte/store";
9
10
  import record from "./img/record.svg";
10
11
 
11
12
  let {
@@ -52,15 +53,9 @@
52
53
  };
53
54
  });
54
55
 
55
- let current_locale: string = $state("en");
56
+ let current_locale: string = $state(get(locale) ?? "en");
56
57
  let current_theme: "light" | "dark" | "system" = $state("system");
57
58
 
58
- locale.subscribe((value) => {
59
- if (value) {
60
- current_locale = value;
61
- }
62
- });
63
-
64
59
  function handleLanguageChange(value: string): void {
65
60
  const new_locale = value;
66
61
  changeLocale(new_locale);
@@ -2,11 +2,8 @@
2
2
  import { _ } from "svelte-i18n";
3
3
  import settings_logo from "./img/settings-logo.svg";
4
4
  import Clear from "./img/clear.svelte";
5
- import { setupi18n } from "../i18n";
6
5
 
7
6
  let { root, onclose }: { root: string; onclose?: () => void } = $props();
8
-
9
- setupi18n();
10
7
  </script>
11
8
 
12
9
  <h2>
@@ -567,6 +567,26 @@ export class DependencyManager {
567
567
  });
568
568
  this.update_loading_stati_state();
569
569
  this.submissions.delete(id);
570
+ // Need to trigger any dependencies that are waiting for this one to complete
571
+ const { failure, all } = this.dependencies_by_fn
572
+ .get(id)
573
+ ?.get_triggers() || { failure: [], all: [] };
574
+ failure.forEach((dep_id) => {
575
+ this.dispatch({
576
+ type: "fn",
577
+ fn_index: dep_id,
578
+ event_data: null,
579
+ target_id: id
580
+ });
581
+ });
582
+ all.forEach((dep_id) => {
583
+ this.dispatch({
584
+ type: "fn",
585
+ fn_index: dep_id,
586
+ event_data: null,
587
+ target_id: id
588
+ });
589
+ });
570
590
  }
571
591
  }
572
592
  }
@@ -1,28 +1,20 @@
1
- import { all_common_keys } from "./i18n";
2
1
  import { _ } from "svelte-i18n";
3
2
  import { get, derived } from "svelte/store";
4
3
  export { Gradio } from "@gradio/utils";
4
+ import { I18N_MARKER, translate_i18n_marker } from "@gradio/utils";
5
5
  export function formatter(value) {
6
6
  if (value == null) {
7
7
  return "";
8
8
  }
9
9
  const string_value = String(value);
10
10
  const translate = get(_);
11
- let direct_translation = translate(string_value);
11
+ if (string_value.includes(I18N_MARKER)) {
12
+ return translate_i18n_marker(string_value, translate);
13
+ }
14
+ const direct_translation = translate(string_value);
12
15
  if (direct_translation !== string_value) {
13
16
  return direct_translation;
14
17
  }
15
- const lower_value = string_value.toLowerCase();
16
- for (const common_key of all_common_keys) {
17
- const key_name = common_key.substring(common_key.indexOf(".") + 1);
18
- if (lower_value === key_name) {
19
- const translation = translate(common_key);
20
- if (translation !== common_key) {
21
- return translation;
22
- }
23
- break;
24
- }
25
- }
26
18
  return string_value;
27
19
  }
28
20
  export const reactive_formatter = derived(_, () => formatter);
@@ -15,11 +15,9 @@ export interface LangsRecord {
15
15
  };
16
16
  }
17
17
  export declare function is_translation_metadata(obj: any): obj is I18nData;
18
- export declare const i18n_marker = "__i18n__";
19
- export declare function translate_if_needed(value: any): string;
18
+ export { I18N_MARKER as i18n_marker } from "@gradio/utils";
20
19
  export declare function process_langs(): LangsRecord;
21
20
  export declare const language_choices: [string, string][];
22
- export declare let all_common_keys: Set<string>;
23
21
  export declare function setupi18n(custom_translations?: Record<string, Record<string, string>>, preferred_locale?: string): Promise<void>;
24
22
  export declare function changeLocale(new_locale: string): void;
25
23
  export declare function get_initial_locale(browser_locale: string | null, available_locales: string[], fallback_locale?: string): string;
package/dist/src/i18n.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { addMessages, init, getLocaleFromNavigator, locale, register, waitLocale } from "svelte-i18n";
2
- import { formatter } from "./gradio_helper";
3
2
  import { loading } from "./lang/loading";
4
3
  const lang_map = {
5
4
  ar: "العربية",
@@ -43,55 +42,7 @@ export function is_translation_metadata(obj) {
43
42
  typeof obj.key === "string";
44
43
  return result;
45
44
  }
46
- export const i18n_marker = "__i18n__";
47
- // handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
48
- export function translate_if_needed(value) {
49
- if (typeof value !== "string") {
50
- return value;
51
- }
52
- const marker_index = value.indexOf(i18n_marker);
53
- if (marker_index === -1) {
54
- return value;
55
- }
56
- try {
57
- const before_marker = marker_index > 0 ? value.substring(0, marker_index) : "";
58
- const after_marker_index = marker_index + i18n_marker.length;
59
- const json_start = value.indexOf("{", after_marker_index);
60
- let json_end = -1;
61
- let bracket_count = 0;
62
- for (let i = json_start; i < value.length; i++) {
63
- if (value[i] === "{")
64
- bracket_count++;
65
- if (value[i] === "}")
66
- bracket_count--;
67
- if (bracket_count === 0) {
68
- json_end = i + 1;
69
- break;
70
- }
71
- }
72
- if (json_end === -1) {
73
- console.error("Could not find end of JSON in i18n string");
74
- return value;
75
- }
76
- const metadata_json = value.substring(json_start, json_end);
77
- const after_json = json_end < value.length ? value.substring(json_end) : "";
78
- try {
79
- const metadata = JSON.parse(metadata_json);
80
- if (metadata && metadata.key) {
81
- const translated = formatter(metadata.key);
82
- return before_marker + translated + after_json;
83
- }
84
- }
85
- catch (jsonError) {
86
- console.error("Error parsing i18n JSON:", jsonError);
87
- }
88
- return value;
89
- }
90
- catch (e) {
91
- console.error("Error processing translation:", e);
92
- return value;
93
- }
94
- }
45
+ export { I18N_MARKER as i18n_marker } from "@gradio/utils";
95
46
  export function process_langs() {
96
47
  const lazy_langs = Object.fromEntries(Object.entries(langs).map(([path, mod]) => [
97
48
  path.split("/").pop().split(".")[0],
@@ -105,7 +56,6 @@ export function process_langs() {
105
56
  const processed_langs = process_langs();
106
57
  const available_locales = Object.keys(processed_langs);
107
58
  export const language_choices = Object.entries(processed_langs).map(([code]) => [lang_map[code] || code, code]);
108
- export let all_common_keys = new Set();
109
59
  let i18n_initialized = false;
110
60
  let previous_translations;
111
61
  function get_lang_from_preferred_locale(header) {
@@ -125,10 +75,13 @@ export async function setupi18n(custom_translations, preferred_locale) {
125
75
  if (i18n_initialized && !should_reinitialize) {
126
76
  return;
127
77
  }
128
- previous_translations = custom_translations;
78
+ const translations_to_use = custom_translations ?? previous_translations ?? {};
79
+ if (custom_translations !== undefined) {
80
+ previous_translations = custom_translations;
81
+ }
129
82
  load_translations({
130
83
  processed_langs,
131
- custom_translations: custom_translations ?? {}
84
+ custom_translations: translations_to_use
132
85
  });
133
86
  let initial_locale = null;
134
87
  const browser_locale = getLocaleFromNavigator();
@@ -1,5 +1,4 @@
1
1
  import { determine_interactivity, get_component, get_inputs_outputs } from "./init_utils";
2
- import { translate_if_needed } from "./i18n";
3
2
  import { tick } from "svelte";
4
3
  import { dequal } from "dequal";
5
4
  import {} from "@gradio/utils";
@@ -130,8 +129,6 @@ export class AppTree {
130
129
  this.root = this.traverse(tree, [
131
130
  (node) => handle_visibility(node, this.#config.api_url),
132
131
  (node) => untrack_children_of_invisible_parents(node, this.components_to_register),
133
- (node) => handle_empty_forms(node, this.components_to_register),
134
- (node) => translate_props(node),
135
132
  (node) => apply_initial_tabs(node, this.initial_tabs),
136
133
  (node) => this.find_attached_events(node, this.#dependency_payload),
137
134
  (node) => untrack_children_of_closed_accordions_or_inactive_tabs(node, this.components_to_register, this.#hidden_on_startup)
@@ -269,7 +266,6 @@ export class AppTree {
269
266
  //@ts-ignore
270
267
  (n) => set_visibility_for_updated_node(n, id, new_state.visible),
271
268
  //@ts-ignore
272
- (n) => update_parent_visibility(n, id, new_state.visible),
273
269
  (n) => handle_visibility(n, this.#config.api_url)
274
270
  ]);
275
271
  await tick();
@@ -302,10 +298,6 @@ export class AppTree {
302
298
  // any values currently in the UI.
303
299
  // @ts-ignore
304
300
  await this.update_visibility(node, new_state);
305
- const parent_node = find_parent(this.root, id);
306
- if (parent_node)
307
- // @ts-ignore
308
- update_parent_visibility(parent_node, id, new_state.visible);
309
301
  }
310
302
  /**
311
303
  * Gets the current state of a component by its ID
@@ -482,61 +474,6 @@ function untrack_children_of_closed_accordions_or_inactive_tabs(node, components
482
474
  }
483
475
  return node;
484
476
  }
485
- function handle_empty_forms(node, components_to_register) {
486
- // Check if the node is visible
487
- if (node.type === "form") {
488
- const all_children_invisible = node.children.every((child) => child.props.shared_props.visible === false);
489
- if (all_children_invisible) {
490
- node.props.shared_props.visible = false;
491
- components_to_register.delete(node.id);
492
- return node;
493
- }
494
- }
495
- return node;
496
- }
497
- function update_parent_visibility(node, child_made_visible, visibility_state) {
498
- // This function was added to address a tricky situation:
499
- // Form components are wrapped in a Form component automatically.
500
- // If all the children of the Form are invisible, the Form itself is marked invisible.
501
- // in AppTree.postprocess -> handle_empty_forms
502
- // This is to avoid rendering empty forms in the UI. They look ugly.
503
- // So what happens when a child inside the Form is made visible again?
504
- // The Form needs to become visible again too.
505
- // If the child is made invisible, the form should be too if all other children are invisible.
506
- // However, we are not doing this now since what we want to do is fetch the latest visibility of all
507
- // the children from the UI. However, get_data only returns the props, not the shared props.
508
- if (node.type === "form" &&
509
- node.children.length &&
510
- node.children.some((child) => child.id === child_made_visible)) {
511
- if (visibility_state === true)
512
- node.props.shared_props.visible = true;
513
- else if (!visibility_state && node.children.length === 1)
514
- node.props.shared_props.visible = "hidden";
515
- }
516
- return node;
517
- }
518
- function translate_props(node) {
519
- const supported_props = [
520
- "description",
521
- "info",
522
- "title",
523
- "placeholder",
524
- "value",
525
- "label"
526
- ];
527
- for (const attr of Object.keys(node.props.shared_props)) {
528
- if (supported_props.includes(attr)) {
529
- // @ts-ignore
530
- node.props.shared_props[attr] = translate_if_needed(node.props.shared_props[attr]);
531
- }
532
- }
533
- for (const attr of Object.keys(node.props.props)) {
534
- if (supported_props.includes(attr)) {
535
- node.props.props[attr] = translate_if_needed(node.props.props[attr]);
536
- }
537
- }
538
- return node;
539
- }
540
477
  function apply_initial_tabs(node, initial_tabs) {
541
478
  if (node.type === "tabs" && node.id in initial_tabs) {
542
479
  const tabs = initial_tabs[node.id].sort((a, b) => a.order - b.order);
package/package.json CHANGED
@@ -1,67 +1,67 @@
1
1
  {
2
2
  "name": "@gradio/core",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "@gradio/atoms": "^0.21.0",
7
- "@gradio/annotatedimage": "^0.11.2",
8
- "@gradio/audio": "^0.22.2",
9
- "@gradio/box": "^0.2.29",
10
- "@gradio/accordion": "^0.5.30",
6
+ "@gradio/accordion": "^0.5.31",
7
+ "@gradio/audio": "^0.22.3",
8
+ "@gradio/atoms": "^0.22.0",
9
+ "@gradio/box": "^0.2.30",
10
+ "@gradio/annotatedimage": "^0.11.3",
11
11
  "@gradio/browserstate": "^0.3.6",
12
12
  "@gradio/button": "^0.6.3",
13
- "@gradio/checkbox": "^0.6.2",
14
- "@gradio/checkboxgroup": "^0.9.2",
15
- "@gradio/code": "^0.17.2",
16
- "@gradio/colorpicker": "^0.5.5",
13
+ "@gradio/checkbox": "^0.6.3",
14
+ "@gradio/checkboxgroup": "^0.9.3",
17
15
  "@gradio/client": "^2.0.4",
16
+ "@gradio/code": "^0.17.3",
17
+ "@gradio/colorpicker": "^0.5.6",
18
+ "@gradio/dataframe": "^0.21.5",
19
+ "@gradio/dataset": "^0.5.4",
20
+ "@gradio/chatbot": "^0.29.4",
18
21
  "@gradio/column": "^0.3.2",
19
- "@gradio/chatbot": "^0.29.3",
20
- "@gradio/dataframe": "^0.21.4",
21
- "@gradio/datetime": "^0.4.2",
22
- "@gradio/dataset": "^0.5.3",
22
+ "@gradio/datetime": "^0.4.3",
23
+ "@gradio/dropdown": "^0.11.4",
24
+ "@gradio/fallback": "^0.4.34",
25
+ "@gradio/file": "^0.14.3",
26
+ "@gradio/form": "^0.3.0",
23
27
  "@gradio/downloadbutton": "^0.4.17",
24
- "@gradio/dropdown": "^0.11.3",
25
- "@gradio/fallback": "^0.4.33",
26
- "@gradio/fileexplorer": "^0.6.2",
27
- "@gradio/form": "^0.2.30",
28
- "@gradio/file": "^0.14.2",
29
- "@gradio/gallery": "^0.17.0",
28
+ "@gradio/gallery": "^0.17.1",
29
+ "@gradio/fileexplorer": "^0.6.3",
30
30
  "@gradio/group": "^0.3.2",
31
- "@gradio/highlightedtext": "^0.11.1",
32
- "@gradio/html": "^0.9.2",
31
+ "@gradio/highlightedtext": "^0.11.2",
32
+ "@gradio/html": "^0.9.3",
33
33
  "@gradio/icons": "^0.15.1",
34
- "@gradio/image": "^0.25.2",
35
- "@gradio/imageeditor": "^0.18.5",
36
- "@gradio/imageslider": "^0.4.2",
37
- "@gradio/json": "^0.7.1",
38
- "@gradio/markdown": "^0.13.27",
39
- "@gradio/label": "^0.6.2",
40
- "@gradio/model3d": "^0.16.3",
41
- "@gradio/multimodaltextbox": "^0.11.5",
42
- "@gradio/nativeplot": "^0.10.1",
43
- "@gradio/number": "^0.8.2",
44
- "@gradio/paramviewer": "^0.9.3",
45
- "@gradio/radio": "^0.9.2",
46
- "@gradio/plot": "^0.10.3",
47
- "@gradio/simpledropdown": "^0.3.33",
48
- "@gradio/sidebar": "^0.2.2",
34
+ "@gradio/imageeditor": "^0.18.6",
35
+ "@gradio/image": "^0.25.3",
36
+ "@gradio/label": "^0.6.3",
37
+ "@gradio/json": "^0.7.2",
38
+ "@gradio/imageslider": "^0.4.3",
39
+ "@gradio/markdown": "^0.13.28",
40
+ "@gradio/model3d": "^0.16.4",
41
+ "@gradio/multimodaltextbox": "^0.11.6",
42
+ "@gradio/paramviewer": "^0.9.4",
43
+ "@gradio/nativeplot": "^0.10.2",
44
+ "@gradio/number": "^0.8.3",
45
+ "@gradio/plot": "^0.10.4",
46
+ "@gradio/radio": "^0.9.3",
49
47
  "@gradio/row": "^0.3.1",
50
- "@gradio/simpleimage": "^0.9.4",
51
- "@gradio/simpletextbox": "^0.3.35",
52
- "@gradio/slider": "^0.7.4",
48
+ "@gradio/simpletextbox": "^0.3.36",
49
+ "@gradio/simpledropdown": "^0.3.34",
50
+ "@gradio/sidebar": "^0.2.3",
51
+ "@gradio/simpleimage": "^0.9.5",
52
+ "@gradio/slider": "^0.7.6",
53
53
  "@gradio/state": "^0.2.2",
54
- "@gradio/statustracker": "^0.12.3",
55
54
  "@gradio/tabitem": "^0.6.5",
56
- "@gradio/tabs": "^0.5.5",
57
- "@gradio/textbox": "^0.13.3",
55
+ "@gradio/statustracker": "^0.12.4",
56
+ "@gradio/tabs": "^0.5.6",
58
57
  "@gradio/theme": "^0.6.1",
59
- "@gradio/upload": "^0.17.5",
58
+ "@gradio/textbox": "^0.13.4",
59
+ "@gradio/upload": "^0.17.6",
60
60
  "@gradio/timer": "^0.4.8",
61
- "@gradio/utils": "^0.11.2",
62
61
  "@gradio/uploadbutton": "^0.9.17",
63
- "@gradio/vibeeditor": "^0.3.4",
64
- "@gradio/video": "^0.20.2"
62
+ "@gradio/utils": "^0.11.3",
63
+ "@gradio/video": "^0.20.3",
64
+ "@gradio/vibeeditor": "^0.3.5"
65
65
  },
66
66
  "msw": {
67
67
  "workerDirectory": "public"
package/src/Blocks.svelte CHANGED
@@ -12,7 +12,6 @@
12
12
  LayoutNode
13
13
  } from "./types";
14
14
  // import type { UpdateTransaction } from "./_init";
15
- import { setupi18n } from "./i18n";
16
15
  import type { ThemeMode, Payload } from "./types";
17
16
  import { Toast } from "@gradio/statustracker";
18
17
  import type { ToastMessage } from "@gradio/statustracker";
package/src/_init.ts CHANGED
@@ -12,8 +12,8 @@ import { load_component } from "virtual:component-loader";
12
12
  import type { client_return } from "@gradio/client";
13
13
  import { create_loading_status_store } from "./stores";
14
14
  import { _ } from "svelte-i18n";
15
- import { i18n_marker } from "./i18n";
16
15
  import type { SharedProps } from "@gradio/utils";
16
+ import { has_i18n_marker } from "@gradio/utils";
17
17
 
18
18
  export interface UpdateTransaction {
19
19
  id: number;
@@ -423,7 +423,7 @@ export function create_components(
423
423
  );
424
424
 
425
425
  instance.props.initial_tabs = child_tab_items?.map((child) => ({
426
- label: child.props.label.includes(i18n_marker) ? "" : child.props.label,
426
+ label: has_i18n_marker(child.props.label) ? "" : child.props.label,
427
427
  id: child.props.id,
428
428
  visible:
429
429
  typeof child.props.visible === "boolean" ||
@@ -6,6 +6,7 @@
6
6
  import { BaseCheckbox as Checkbox } from "@gradio/checkbox";
7
7
  import { language_choices, changeLocale } from "../i18n";
8
8
  import { locale, _ } from "svelte-i18n";
9
+ import { get } from "svelte/store";
9
10
  import record from "./img/record.svg";
10
11
 
11
12
  let {
@@ -52,15 +53,9 @@
52
53
  };
53
54
  });
54
55
 
55
- let current_locale: string = $state("en");
56
+ let current_locale: string = $state(get(locale) ?? "en");
56
57
  let current_theme: "light" | "dark" | "system" = $state("system");
57
58
 
58
- locale.subscribe((value) => {
59
- if (value) {
60
- current_locale = value;
61
- }
62
- });
63
-
64
59
  function handleLanguageChange(value: string): void {
65
60
  const new_locale = value;
66
61
  changeLocale(new_locale);
@@ -2,11 +2,8 @@
2
2
  import { _ } from "svelte-i18n";
3
3
  import settings_logo from "./img/settings-logo.svg";
4
4
  import Clear from "./img/clear.svelte";
5
- import { setupi18n } from "../i18n";
6
5
 
7
6
  let { root, onclose }: { root: string; onclose?: () => void } = $props();
8
-
9
- setupi18n();
10
7
  </script>
11
8
 
12
9
  <h2>
package/src/dependency.ts CHANGED
@@ -806,6 +806,26 @@ export class DependencyManager {
806
806
  });
807
807
  this.update_loading_stati_state();
808
808
  this.submissions.delete(id);
809
+ // Need to trigger any dependencies that are waiting for this one to complete
810
+ const { failure, all } = this.dependencies_by_fn
811
+ .get(id)
812
+ ?.get_triggers() || { failure: [], all: [] };
813
+ failure.forEach((dep_id) => {
814
+ this.dispatch({
815
+ type: "fn",
816
+ fn_index: dep_id,
817
+ event_data: null,
818
+ target_id: id
819
+ });
820
+ });
821
+ all.forEach((dep_id) => {
822
+ this.dispatch({
823
+ type: "fn",
824
+ fn_index: dep_id,
825
+ event_data: null,
826
+ target_id: id
827
+ });
828
+ });
809
829
  }
810
830
  }
811
831
  }
@@ -1,7 +1,7 @@
1
- import { all_common_keys } from "./i18n";
2
1
  import { _ } from "svelte-i18n";
3
2
  import { get, derived } from "svelte/store";
4
3
  export { Gradio } from "@gradio/utils";
4
+ import { I18N_MARKER, translate_i18n_marker } from "@gradio/utils";
5
5
 
6
6
  export type I18nFormatter = typeof formatter;
7
7
 
@@ -12,27 +12,15 @@ export function formatter(value: string | null | undefined): string {
12
12
  const string_value = String(value);
13
13
  const translate = get(_);
14
14
 
15
- let direct_translation = translate(string_value);
15
+ if (string_value.includes(I18N_MARKER)) {
16
+ return translate_i18n_marker(string_value, translate);
17
+ }
16
18
 
19
+ const direct_translation = translate(string_value);
17
20
  if (direct_translation !== string_value) {
18
21
  return direct_translation;
19
22
  }
20
23
 
21
- const lower_value = string_value.toLowerCase();
22
-
23
- for (const common_key of all_common_keys) {
24
- const key_name = common_key.substring(common_key.indexOf(".") + 1);
25
-
26
- if (lower_value === key_name) {
27
- const translation = translate(common_key);
28
-
29
- if (translation !== common_key) {
30
- return translation;
31
- }
32
- break;
33
- }
34
- }
35
-
36
24
  return string_value;
37
25
  }
38
26
 
package/src/i18n.test.ts CHANGED
@@ -11,12 +11,12 @@ import { Lang, process_langs } from "./i18n";
11
11
  import languagesByAnyCode from "wikidata-lang/indexes/by_any_code";
12
12
  import BCP47 from "./lang/BCP47_codes";
13
13
  import {
14
- translate_if_needed,
15
14
  get_initial_locale,
16
15
  load_translations,
17
16
  changeLocale,
18
17
  is_translation_metadata
19
18
  } from "./i18n";
19
+ import { formatter } from "./gradio_helper";
20
20
  import { loading } from "./lang/loading";
21
21
 
22
22
  const loading_count = Object.keys(loading).length;
@@ -28,8 +28,16 @@ vi.mock("svelte-i18n", () => ({
28
28
  init: vi.fn().mockResolvedValue(undefined)
29
29
  }));
30
30
 
31
+ const mock_translations: Record<string, string> = {
32
+ "common.submit": "Submit",
33
+ "common.name": "Name",
34
+ "common.greeting": "Hello",
35
+ "common.submit_es": "Enviar",
36
+ "common.name_es": "Nombre"
37
+ };
38
+
31
39
  vi.mock("svelte/store", () => ({
32
- get: vi.fn((store) => store),
40
+ get: vi.fn(() => (key: string) => mock_translations[key] ?? key),
33
41
  derived: vi.fn()
34
42
  }));
35
43
 
@@ -66,13 +74,6 @@ describe("i18n", () => {
66
74
  );
67
75
  });
68
76
 
69
- describe("basic functions", () => {
70
- test("translate_if_needed handles regular strings", () => {
71
- const regularString = "hello world";
72
- expect(translate_if_needed(regularString)).toBe(regularString);
73
- });
74
- });
75
-
76
77
  describe("locale management", () => {
77
78
  test("get_initial_locale returns browser locale when available", () => {
78
79
  const result = get_initial_locale("fr", ["en", "fr", "de"]);
@@ -155,4 +156,35 @@ describe("i18n", () => {
155
156
  expect(Boolean(is_translation_metadata("not an object"))).toBe(false);
156
157
  });
157
158
  });
159
+
160
+ describe("formatter", () => {
161
+ test("translates i18n markers", () => {
162
+ expect(formatter('__i18n__{"key":"common.submit"}')).toBe("Submit");
163
+ expect(formatter('Click: __i18n__{"key":"common.submit"}')).toBe(
164
+ "Click: Submit"
165
+ );
166
+ expect(formatter('__i18n__{"key":"common.name"} field')).toBe(
167
+ "Name field"
168
+ );
169
+ expect(formatter('__i18n__{"key":"common.submit_es"}')).toBe("Enviar");
170
+ });
171
+
172
+ test("returns key when no translation exists", () => {
173
+ expect(formatter('__i18n__{"key":"unknown.key"}')).toBe("unknown.key");
174
+ });
175
+
176
+ test("handles null, undefined, and plain text", () => {
177
+ expect(formatter(null)).toBe("");
178
+ expect(formatter(undefined)).toBe("");
179
+ expect(formatter("Hello world")).toBe("Hello world");
180
+ });
181
+
182
+ test("handles malformed markers", () => {
183
+ expect(formatter("__i18n__")).toBe("__i18n__");
184
+ expect(formatter('__i18n__{"key":"test.key"')).toBe(
185
+ '__i18n__{"key":"test.key"'
186
+ );
187
+ expect(formatter("__i18n__{invalid}")).toBe("__i18n__{invalid}");
188
+ });
189
+ });
158
190
  });
package/src/i18n.ts CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  register,
7
7
  waitLocale
8
8
  } from "svelte-i18n";
9
- import { formatter } from "./gradio_helper";
10
9
  import { loading } from "./lang/loading";
11
10
 
12
11
  const lang_map = {
@@ -71,63 +70,7 @@ export function is_translation_metadata(obj: any): obj is I18nData {
71
70
  return result;
72
71
  }
73
72
 
74
- export const i18n_marker = "__i18n__";
75
-
76
- // handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
77
- export function translate_if_needed(value: any): string {
78
- if (typeof value !== "string") {
79
- return value;
80
- }
81
-
82
- const marker_index = value.indexOf(i18n_marker);
83
-
84
- if (marker_index === -1) {
85
- return value;
86
- }
87
-
88
- try {
89
- const before_marker =
90
- marker_index > 0 ? value.substring(0, marker_index) : "";
91
-
92
- const after_marker_index = marker_index + i18n_marker.length;
93
- const json_start = value.indexOf("{", after_marker_index);
94
- let json_end = -1;
95
- let bracket_count = 0;
96
-
97
- for (let i = json_start; i < value.length; i++) {
98
- if (value[i] === "{") bracket_count++;
99
- if (value[i] === "}") bracket_count--;
100
- if (bracket_count === 0) {
101
- json_end = i + 1;
102
- break;
103
- }
104
- }
105
-
106
- if (json_end === -1) {
107
- console.error("Could not find end of JSON in i18n string");
108
- return value;
109
- }
110
-
111
- const metadata_json = value.substring(json_start, json_end);
112
- const after_json = json_end < value.length ? value.substring(json_end) : "";
113
-
114
- try {
115
- const metadata = JSON.parse(metadata_json);
116
-
117
- if (metadata && metadata.key) {
118
- const translated = formatter(metadata.key);
119
- return before_marker + translated + after_json;
120
- }
121
- } catch (jsonError) {
122
- console.error("Error parsing i18n JSON:", jsonError);
123
- }
124
-
125
- return value;
126
- } catch (e) {
127
- console.error("Error processing translation:", e);
128
- return value;
129
- }
130
- }
73
+ export { I18N_MARKER as i18n_marker } from "@gradio/utils";
131
74
 
132
75
  export function process_langs(): LangsRecord {
133
76
  const lazy_langs = Object.fromEntries(
@@ -150,8 +93,6 @@ export const language_choices: [string, string][] = Object.entries(
150
93
  processed_langs
151
94
  ).map(([code]) => [lang_map[code as keyof typeof lang_map] || code, code]);
152
95
 
153
- export let all_common_keys: Set<string> = new Set();
154
-
155
96
  let i18n_initialized = false;
156
97
  let previous_translations: Record<string, Record<string, string>> | undefined;
157
98
 
@@ -183,15 +124,21 @@ export async function setupi18n(
183
124
  return;
184
125
  }
185
126
 
186
- previous_translations = custom_translations;
127
+ const translations_to_use =
128
+ custom_translations ?? previous_translations ?? {};
129
+
130
+ if (custom_translations !== undefined) {
131
+ previous_translations = custom_translations;
132
+ }
187
133
 
188
134
  load_translations({
189
135
  processed_langs,
190
- custom_translations: custom_translations ?? {}
136
+ custom_translations: translations_to_use
191
137
  });
192
138
 
193
139
  let initial_locale: string | null = null;
194
140
  const browser_locale = getLocaleFromNavigator();
141
+
195
142
  if (preferred_locale) {
196
143
  initial_locale = get_lang_from_preferred_locale(preferred_locale);
197
144
  } else {
@@ -3,7 +3,6 @@ import {
3
3
  get_component,
4
4
  get_inputs_outputs
5
5
  } from "./init_utils";
6
- import { translate_if_needed } from "./i18n";
7
6
  import { tick } from "svelte";
8
7
  import { dequal } from "dequal";
9
8
 
@@ -222,8 +221,6 @@ export class AppTree {
222
221
  node,
223
222
  this.components_to_register
224
223
  ),
225
- (node) => handle_empty_forms(node, this.components_to_register),
226
- (node) => translate_props(node),
227
224
  (node) => apply_initial_tabs(node, this.initial_tabs),
228
225
  (node) => this.find_attached_events(node, this.#dependency_payload),
229
226
  (node) =>
@@ -422,7 +419,6 @@ export class AppTree {
422
419
  //@ts-ignore
423
420
  (n) => set_visibility_for_updated_node(n, id, new_state.visible),
424
421
  //@ts-ignore
425
- (n) => update_parent_visibility(n, id, new_state.visible),
426
422
  (n) => handle_visibility(n, this.#config.api_url)
427
423
  ]);
428
424
  await tick();
@@ -453,10 +449,6 @@ export class AppTree {
453
449
  // any values currently in the UI.
454
450
  // @ts-ignore
455
451
  await this.update_visibility(node, new_state);
456
- const parent_node = find_parent(this.root!, id);
457
- if (parent_node)
458
- // @ts-ignore
459
- update_parent_visibility(parent_node, id, new_state.visible);
460
452
  }
461
453
 
462
454
  /**
@@ -701,78 +693,6 @@ function untrack_children_of_closed_accordions_or_inactive_tabs(
701
693
  return node;
702
694
  }
703
695
 
704
- function handle_empty_forms(
705
- node: ProcessedComponentMeta,
706
- components_to_register: Set<number>
707
- ): ProcessedComponentMeta {
708
- // Check if the node is visible
709
- if (node.type === "form") {
710
- const all_children_invisible = node.children.every(
711
- (child) => child.props.shared_props.visible === false
712
- );
713
-
714
- if (all_children_invisible) {
715
- node.props.shared_props.visible = false;
716
- components_to_register.delete(node.id);
717
- return node;
718
- }
719
- }
720
-
721
- return node;
722
- }
723
-
724
- function update_parent_visibility(
725
- node: ProcessedComponentMeta,
726
- child_made_visible: number,
727
- visibility_state: boolean | "hidden"
728
- ): ProcessedComponentMeta {
729
- // This function was added to address a tricky situation:
730
- // Form components are wrapped in a Form component automatically.
731
- // If all the children of the Form are invisible, the Form itself is marked invisible.
732
- // in AppTree.postprocess -> handle_empty_forms
733
- // This is to avoid rendering empty forms in the UI. They look ugly.
734
- // So what happens when a child inside the Form is made visible again?
735
- // The Form needs to become visible again too.
736
- // If the child is made invisible, the form should be too if all other children are invisible.
737
- // However, we are not doing this now since what we want to do is fetch the latest visibility of all
738
- // the children from the UI. However, get_data only returns the props, not the shared props.
739
- if (
740
- node.type === "form" &&
741
- node.children.length &&
742
- node.children.some((child) => child.id === child_made_visible)
743
- ) {
744
- if (visibility_state === true) node.props.shared_props.visible = true;
745
- else if (!visibility_state && node.children.length === 1)
746
- node.props.shared_props.visible = "hidden";
747
- }
748
- return node;
749
- }
750
-
751
- function translate_props(node: ProcessedComponentMeta): ProcessedComponentMeta {
752
- const supported_props = [
753
- "description",
754
- "info",
755
- "title",
756
- "placeholder",
757
- "value",
758
- "label"
759
- ];
760
- for (const attr of Object.keys(node.props.shared_props)) {
761
- if (supported_props.includes(attr as string)) {
762
- // @ts-ignore
763
- node.props.shared_props[attr] = translate_if_needed(
764
- node.props.shared_props[attr as keyof SharedProps]
765
- );
766
- }
767
- }
768
- for (const attr of Object.keys(node.props.props)) {
769
- if (supported_props.includes(attr as string)) {
770
- node.props.props[attr] = translate_if_needed(node.props.props[attr]);
771
- }
772
- }
773
- return node;
774
- }
775
-
776
696
  function apply_initial_tabs(
777
697
  node: ProcessedComponentMeta,
778
698
  initial_tabs: Record<number, Tab[]>