@gradio/core 0.16.1 → 0.17.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 +36 -0
- package/dist/src/Blocks.svelte +6 -2
- package/dist/src/Render.svelte +2 -2
- package/dist/src/RenderComponent.svelte +21 -1
- package/dist/src/api_docs/ApiDocs.svelte +6 -1
- package/dist/src/gradio_helper.d.ts +1 -11
- package/dist/src/gradio_helper.js +3 -19
- package/dist/src/i18n.d.ts +12 -5
- package/dist/src/i18n.js +93 -12
- package/dist/src/init.js +36 -20
- package/dist/src/lang/en.json +2 -1
- package/dist/src/lang/es.json +2 -1
- package/dist/src/lang/fr.json +2 -1
- package/dist/src/lang/ja.json +2 -1
- package/dist/src/lang/ko.json +2 -1
- package/dist/src/lang/lt.json +2 -1
- package/dist/src/lang/nb.json +2 -1
- package/dist/src/lang/nl.json +2 -1
- package/dist/src/lang/pl.json +2 -1
- package/dist/src/lang/pt-BR.json +1 -0
- package/dist/src/lang/pt.json +2 -1
- package/dist/src/lang/ro.json +2 -1
- package/dist/src/lang/ru.json +2 -1
- package/dist/src/lang/sv.json +2 -1
- package/dist/src/lang/ta.json +2 -1
- package/dist/src/lang/th.json +2 -1
- package/dist/src/lang/tr.json +2 -1
- package/dist/src/lang/uk.json +2 -1
- package/dist/src/lang/ur.json +2 -1
- package/dist/src/lang/uz.json +2 -1
- package/dist/src/lang/zh-CN.json +2 -1
- package/dist/src/lang/zh-TW.json +2 -1
- package/package.json +52 -52
- package/src/Blocks.svelte +7 -3
- package/src/Render.svelte +2 -2
- package/src/RenderComponent.svelte +21 -1
- package/src/api_docs/ApiDocs.svelte +7 -1
- package/src/gradio_helper.ts +5 -21
- package/src/i18n.test.ts +120 -1
- package/src/i18n.ts +126 -24
- package/src/init.ts +47 -26
- package/src/lang/en.json +2 -1
- package/src/lang/es.json +2 -1
- package/src/lang/fr.json +2 -1
- package/src/lang/ja.json +2 -1
- package/src/lang/ko.json +2 -1
- package/src/lang/lt.json +2 -1
- package/src/lang/nb.json +2 -1
- package/src/lang/nl.json +2 -1
- package/src/lang/pl.json +2 -1
- package/src/lang/pt-BR.json +1 -0
- package/src/lang/pt.json +2 -1
- package/src/lang/ro.json +2 -1
- package/src/lang/ru.json +2 -1
- package/src/lang/sv.json +2 -1
- package/src/lang/ta.json +2 -1
- package/src/lang/th.json +2 -1
- package/src/lang/tr.json +2 -1
- package/src/lang/uk.json +2 -1
- package/src/lang/ur.json +2 -1
- package/src/lang/uz.json +2 -1
- package/src/lang/zh-CN.json +2 -1
- package/src/lang/zh-TW.json +2 -1
package/dist/src/lang/uk.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "Є помилка виконання",
|
|
98
98
|
"space_not_working": "\"Space не працює, оскільки\" {0}",
|
|
99
99
|
"space_paused": "Space призупинено",
|
|
100
|
-
"use_via_api": "Використовувати через API"
|
|
100
|
+
"use_via_api": "Використовувати через API",
|
|
101
|
+
"use_via_api_or_mcp": "Використовувати через API або MCP"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "Завантаження..."
|
package/dist/src/lang/ur.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "رن ٹائم میں خطا ہے",
|
|
98
98
|
"space_not_working": "\"Space کام نہیں کر رہا ہے کیونکہ\" {0}",
|
|
99
99
|
"space_paused": "Space موقوف ہے",
|
|
100
|
-
"use_via_api": "API کے ذریعے استعمال کریں"
|
|
100
|
+
"use_via_api": "API کے ذریعے استعمال کریں",
|
|
101
|
+
"use_via_api_or_mcp": "API یا MCP کے ذریعے استعمال کریں"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "اپلوڈ ہو رہا ہے..."
|
package/dist/src/lang/uz.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "Bajarilish vaqti xatosi mavjud",
|
|
98
98
|
"space_not_working": "\"Space ishlamayapti, chunki\" {0}",
|
|
99
99
|
"space_paused": "Space to'xtatilgan",
|
|
100
|
-
"use_via_api": "API orqali foydalaning"
|
|
100
|
+
"use_via_api": "API orqali foydalaning",
|
|
101
|
+
"use_via_api_or_mcp": "API yoki MCP orqali foydalaning"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "Yuklanmoqda..."
|
package/dist/src/lang/zh-CN.json
CHANGED
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"runtime_error": "存在运行时错误",
|
|
88
88
|
"space_not_working": "\"Space 无法工作,原因:\" {0}",
|
|
89
89
|
"space_paused": "Space 已暂停",
|
|
90
|
-
"use_via_api": "通过 API 使用"
|
|
90
|
+
"use_via_api": "通过 API 使用",
|
|
91
|
+
"use_via_api_or_mcp": "通过 API 或 MCP 使用"
|
|
91
92
|
},
|
|
92
93
|
"file": {
|
|
93
94
|
"uploading": "正在上传..."
|
package/dist/src/lang/zh-TW.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "有執行時錯誤",
|
|
98
98
|
"space_not_working": "\"Space 無法運作,因為\" {0}",
|
|
99
99
|
"space_paused": "Space 已暫停",
|
|
100
|
-
"use_via_api": "透過 API 使用"
|
|
100
|
+
"use_via_api": "透過 API 使用",
|
|
101
|
+
"use_via_api_or_mcp": "透過 API 或 MCP 使用"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "上傳中..."
|
package/package.json
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"devDependencies": {
|
|
6
|
-
"@gradio/
|
|
7
|
-
"@gradio/
|
|
8
|
-
"@gradio/atoms": "^0.16.
|
|
9
|
-
"@gradio/
|
|
10
|
-
"@gradio/box": "^0.2.
|
|
11
|
-
"@gradio/chatbot": "^0.26.
|
|
12
|
-
"@gradio/
|
|
13
|
-
"@gradio/
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/
|
|
16
|
-
"@gradio/code": "^0.14.
|
|
17
|
-
"@gradio/colorpicker": "^0.4.
|
|
6
|
+
"@gradio/accordion": "^0.5.15",
|
|
7
|
+
"@gradio/annotatedimage": "^0.9.19",
|
|
8
|
+
"@gradio/atoms": "^0.16.1",
|
|
9
|
+
"@gradio/audio": "^0.17.14",
|
|
10
|
+
"@gradio/box": "^0.2.19",
|
|
11
|
+
"@gradio/chatbot": "^0.26.7",
|
|
12
|
+
"@gradio/button": "^0.5.0",
|
|
13
|
+
"@gradio/checkboxgroup": "^0.6.21",
|
|
14
|
+
"@gradio/client": "^1.15.0",
|
|
15
|
+
"@gradio/checkbox": "^0.4.21",
|
|
16
|
+
"@gradio/code": "^0.14.4",
|
|
17
|
+
"@gradio/colorpicker": "^0.4.21",
|
|
18
18
|
"@gradio/column": "^0.2.0",
|
|
19
|
-
"@gradio/dataframe": "^0.17.
|
|
20
|
-
"@gradio/dataset": "^0.4.
|
|
21
|
-
"@gradio/
|
|
22
|
-
"@gradio/
|
|
23
|
-
"@gradio/dropdown": "^0.9.
|
|
24
|
-
"@gradio/
|
|
25
|
-
"@gradio/
|
|
26
|
-
"@gradio/
|
|
27
|
-
"@gradio/
|
|
28
|
-
"@gradio/
|
|
29
|
-
"@gradio/
|
|
19
|
+
"@gradio/dataframe": "^0.17.12",
|
|
20
|
+
"@gradio/dataset": "^0.4.19",
|
|
21
|
+
"@gradio/datetime": "^0.3.13",
|
|
22
|
+
"@gradio/downloadbutton": "^0.4.0",
|
|
23
|
+
"@gradio/dropdown": "^0.9.21",
|
|
24
|
+
"@gradio/file": "^0.12.18",
|
|
25
|
+
"@gradio/form": "^0.2.19",
|
|
26
|
+
"@gradio/fallback": "^0.4.21",
|
|
27
|
+
"@gradio/fileexplorer": "^0.5.29",
|
|
28
|
+
"@gradio/highlightedtext": "^0.9.4",
|
|
29
|
+
"@gradio/gallery": "^0.15.19",
|
|
30
|
+
"@gradio/html": "^0.6.13",
|
|
30
31
|
"@gradio/group": "^0.2.0",
|
|
31
32
|
"@gradio/icons": "^0.12.0",
|
|
32
|
-
"@gradio/image": "^0.22.
|
|
33
|
-
"@gradio/
|
|
34
|
-
"@gradio/
|
|
35
|
-
"@gradio/
|
|
36
|
-
"@gradio/json": "^0.5.
|
|
33
|
+
"@gradio/image": "^0.22.6",
|
|
34
|
+
"@gradio/imageeditor": "^0.15.0",
|
|
35
|
+
"@gradio/imageslider": "^0.2.2",
|
|
36
|
+
"@gradio/label": "^0.5.13",
|
|
37
|
+
"@gradio/json": "^0.5.22",
|
|
37
38
|
"@gradio/browserstate": "^0.3.2",
|
|
38
|
-
"@gradio/markdown": "^0.13.
|
|
39
|
-
"@gradio/
|
|
40
|
-
"@gradio/
|
|
41
|
-
"@gradio/
|
|
42
|
-
"@gradio/
|
|
43
|
-
"@gradio/
|
|
44
|
-
"@gradio/
|
|
45
|
-
"@gradio/radio": "^0.7.
|
|
46
|
-
"@gradio/plot": "^0.9.15",
|
|
39
|
+
"@gradio/markdown": "^0.13.12",
|
|
40
|
+
"@gradio/multimodaltextbox": "^0.10.6",
|
|
41
|
+
"@gradio/nativeplot": "^0.5.16",
|
|
42
|
+
"@gradio/paramviewer": "^0.7.9",
|
|
43
|
+
"@gradio/number": "^0.5.21",
|
|
44
|
+
"@gradio/model3d": "^0.14.13",
|
|
45
|
+
"@gradio/plot": "^0.9.16",
|
|
46
|
+
"@gradio/radio": "^0.7.4",
|
|
47
47
|
"@gradio/row": "^0.2.1",
|
|
48
|
-
"@gradio/sidebar": "^0.1.
|
|
49
|
-
"@gradio/simpledropdown": "^0.3.
|
|
50
|
-
"@gradio/
|
|
51
|
-
"@gradio/
|
|
52
|
-
"@gradio/
|
|
48
|
+
"@gradio/sidebar": "^0.1.13",
|
|
49
|
+
"@gradio/simpledropdown": "^0.3.21",
|
|
50
|
+
"@gradio/sketchbox": "^0.6.8",
|
|
51
|
+
"@gradio/simpletextbox": "^0.3.21",
|
|
52
|
+
"@gradio/simpleimage": "^0.8.29",
|
|
53
|
+
"@gradio/slider": "^0.6.9",
|
|
54
|
+
"@gradio/statustracker": "^0.10.11",
|
|
53
55
|
"@gradio/state": "^0.1.2",
|
|
54
|
-
"@gradio/
|
|
55
|
-
"@gradio/
|
|
56
|
-
"@gradio/
|
|
57
|
-
"@gradio/tabs": "^0.4.3",
|
|
58
|
-
"@gradio/textbox": "^0.10.10",
|
|
56
|
+
"@gradio/tabitem": "^0.4.4",
|
|
57
|
+
"@gradio/tabs": "^0.4.4",
|
|
58
|
+
"@gradio/textbox": "^0.10.11",
|
|
59
59
|
"@gradio/theme": "^0.4.0",
|
|
60
60
|
"@gradio/timer": "^0.4.5",
|
|
61
|
-
"@gradio/upload": "^0.16.
|
|
62
|
-
"@gradio/uploadbutton": "^0.
|
|
63
|
-
"@gradio/wasm": "^0.18.1",
|
|
61
|
+
"@gradio/upload": "^0.16.5",
|
|
62
|
+
"@gradio/uploadbutton": "^0.9.0",
|
|
64
63
|
"@gradio/utils": "^0.10.2",
|
|
65
|
-
"@gradio/
|
|
64
|
+
"@gradio/wasm": "^0.18.1",
|
|
65
|
+
"@gradio/video": "^0.14.14"
|
|
66
66
|
},
|
|
67
67
|
"msw": {
|
|
68
68
|
"workerDirectory": "public"
|
package/src/Blocks.svelte
CHANGED
|
@@ -26,8 +26,6 @@
|
|
|
26
26
|
StatusMessage
|
|
27
27
|
} from "@gradio/client";
|
|
28
28
|
|
|
29
|
-
setupi18n();
|
|
30
|
-
|
|
31
29
|
export let root: string;
|
|
32
30
|
export let components: ComponentMeta[];
|
|
33
31
|
export let layout: LayoutNode;
|
|
@@ -52,6 +50,8 @@
|
|
|
52
50
|
export let initial_layout: ComponentMeta | undefined = undefined;
|
|
53
51
|
export let css: string | null | undefined = null;
|
|
54
52
|
|
|
53
|
+
setupi18n(app.config?.i18n_translations ?? undefined);
|
|
54
|
+
|
|
55
55
|
let {
|
|
56
56
|
layout: _layout,
|
|
57
57
|
targets,
|
|
@@ -822,7 +822,11 @@
|
|
|
822
822
|
}}
|
|
823
823
|
class="show-api"
|
|
824
824
|
>
|
|
825
|
-
{
|
|
825
|
+
{#if app.config?.mcp_server}
|
|
826
|
+
{$_("errors.use_via_api_or_mcp")}
|
|
827
|
+
{:else}
|
|
828
|
+
{$_("errors.use_via_api")}
|
|
829
|
+
{/if}
|
|
826
830
|
<img src={api_logo} alt={$_("common.logo")} />
|
|
827
831
|
</button>
|
|
828
832
|
<div class="divider show-api-divider">·</div>
|
package/src/Render.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Gradio,
|
|
2
|
+
import { Gradio, reactive_formatter } from "./gradio_helper";
|
|
3
3
|
import { onMount, createEventDispatcher, setContext } from "svelte";
|
|
4
4
|
import type { ComponentMeta, ThemeMode } from "./types";
|
|
5
5
|
import type { Client } from "@gradio/client";
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
root,
|
|
71
71
|
autoscroll,
|
|
72
72
|
max_file_size,
|
|
73
|
-
|
|
73
|
+
$reactive_formatter,
|
|
74
74
|
client,
|
|
75
75
|
load_component
|
|
76
76
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<svelte:options immutable={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
-
import type { Gradio } from "./gradio_helper";
|
|
5
4
|
import type { ComponentMeta, ThemeMode } from "./types";
|
|
6
5
|
import type { SvelteComponent, ComponentType } from "svelte";
|
|
6
|
+
import { translate_if_needed } from "./i18n";
|
|
7
7
|
// @ts-ignore
|
|
8
8
|
import { bind, binding_callbacks } from "svelte/internal";
|
|
9
9
|
|
|
@@ -50,6 +50,26 @@
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const _component = wrap(component);
|
|
53
|
+
|
|
54
|
+
const supported_props = [
|
|
55
|
+
"description",
|
|
56
|
+
"info",
|
|
57
|
+
"title",
|
|
58
|
+
"placeholder",
|
|
59
|
+
"value",
|
|
60
|
+
"label"
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
function translate_prop(obj: SvelteRestProps): void {
|
|
64
|
+
for (const key in obj) {
|
|
65
|
+
if (supported_props.includes(key as string)) {
|
|
66
|
+
obj[key] = translate_if_needed(obj[key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$: translate_prop($$restProps);
|
|
72
|
+
$: value = translate_if_needed(value);
|
|
53
73
|
</script>
|
|
54
74
|
|
|
55
75
|
<!-- {#if visible} -->
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
const bash_docs =
|
|
36
36
|
"https://www.gradio.app/guides/querying-gradio-apps-with-curl";
|
|
37
37
|
const spaces_docs_suffix = "#connecting-to-a-hugging-face-space";
|
|
38
|
+
const mcp_docs =
|
|
39
|
+
"https://www.gradio.app/guides/building-mcp-server-with-gradio";
|
|
38
40
|
|
|
39
41
|
let api_count = dependencies.filter(
|
|
40
42
|
(dependency) => dependency.show_api
|
|
@@ -379,7 +381,11 @@
|
|
|
379
381
|
</code>
|
|
380
382
|
</Block>
|
|
381
383
|
<p> </p>
|
|
382
|
-
<p
|
|
384
|
+
<p>
|
|
385
|
+
<a href={mcp_docs} target="_blank">
|
|
386
|
+
Read more about MCP in the Gradio docs
|
|
387
|
+
</a>
|
|
388
|
+
</p>
|
|
383
389
|
{:else}
|
|
384
390
|
This Gradio app can also serve as an MCP server, with an MCP
|
|
385
391
|
tool corresponding to each API endpoint. To enable this, launch
|
package/src/gradio_helper.ts
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
import { format, _ } from "svelte-i18n";
|
|
2
|
-
import { get } from "svelte/store";
|
|
3
1
|
import { all_common_keys } from "./i18n";
|
|
4
|
-
|
|
2
|
+
import { _ } from "svelte-i18n";
|
|
3
|
+
import { get, derived } from "svelte/store";
|
|
5
4
|
export { Gradio } from "@gradio/utils";
|
|
5
|
+
|
|
6
6
|
export type I18nFormatter = typeof formatter;
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* i18n formatter with fallback to svelte-i18n's format function.
|
|
10
|
-
*
|
|
11
|
-
* @param value - The string to translate or format
|
|
12
|
-
* @returns The translated string
|
|
13
|
-
*
|
|
14
|
-
* This formatter attempts translation in the following order:
|
|
15
|
-
* 1. Direct translation of the input string
|
|
16
|
-
* 2. Checks if input matches any common key names
|
|
17
|
-
* 3. Falls back to svelte-i18n's format function
|
|
18
|
-
*/
|
|
19
8
|
export function formatter(value: string | null | undefined): string {
|
|
20
9
|
if (value == null) {
|
|
21
10
|
return "";
|
|
22
11
|
}
|
|
23
12
|
const string_value = String(value);
|
|
24
13
|
const translate = get(_);
|
|
25
|
-
const initial_formatter = get(format);
|
|
26
14
|
|
|
27
15
|
let direct_translation = translate(string_value);
|
|
28
16
|
|
|
@@ -45,11 +33,7 @@ export function formatter(value: string | null | undefined): string {
|
|
|
45
33
|
}
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
// fall back to the svelte-i18n formatter to maintain compatibility
|
|
49
|
-
const formatted = initial_formatter(string_value);
|
|
50
|
-
if (formatted !== string_value) {
|
|
51
|
-
return formatted;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
return string_value;
|
|
55
37
|
}
|
|
38
|
+
|
|
39
|
+
export const reactive_formatter = derived(_, () => formatter);
|
package/src/i18n.test.ts
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
test,
|
|
4
|
+
expect,
|
|
5
|
+
assert,
|
|
6
|
+
vi,
|
|
7
|
+
beforeEach,
|
|
8
|
+
afterEach
|
|
9
|
+
} from "vitest";
|
|
2
10
|
import { process_langs } from "./i18n";
|
|
3
11
|
import languagesByAnyCode from "wikidata-lang/indexes/by_any_code";
|
|
4
12
|
import BCP47 from "./lang/BCP47_codes";
|
|
13
|
+
import {
|
|
14
|
+
translate_if_needed,
|
|
15
|
+
get_initial_locale,
|
|
16
|
+
load_translations,
|
|
17
|
+
changeLocale,
|
|
18
|
+
is_translation_metadata
|
|
19
|
+
} from "./i18n";
|
|
20
|
+
|
|
21
|
+
vi.mock("svelte-i18n", () => ({
|
|
22
|
+
locale: { set: vi.fn() },
|
|
23
|
+
_: vi.fn((key) => `translated_${key}`),
|
|
24
|
+
addMessages: vi.fn(),
|
|
25
|
+
init: vi.fn().mockResolvedValue(undefined)
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("svelte/store", () => ({
|
|
29
|
+
get: vi.fn((store) => store),
|
|
30
|
+
derived: vi.fn()
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { locale, init, addMessages } from "svelte-i18n";
|
|
5
34
|
|
|
6
35
|
describe("i18n", () => {
|
|
7
36
|
test("languages are loaded correctly", () => {
|
|
@@ -24,4 +53,94 @@ describe("i18n", () => {
|
|
|
24
53
|
assert.ok(translation.common);
|
|
25
54
|
});
|
|
26
55
|
});
|
|
56
|
+
|
|
57
|
+
describe("basic functions", () => {
|
|
58
|
+
test("translate_if_needed handles regular strings", () => {
|
|
59
|
+
const regularString = "hello world";
|
|
60
|
+
expect(translate_if_needed(regularString)).toBe(regularString);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("locale management", () => {
|
|
65
|
+
test("get_initial_locale returns browser locale when available", () => {
|
|
66
|
+
const result = get_initial_locale("fr", ["en", "fr", "de"]);
|
|
67
|
+
expect(result).toBe("fr");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("get_initial_locale falls back to fallback locale when browser locale not available", () => {
|
|
71
|
+
const result = get_initial_locale("es", ["en", "fr", "de"]);
|
|
72
|
+
expect(result).toBe("en");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("get_initial_locale falls back to fallback locale when browser locale is null", () => {
|
|
76
|
+
const result = get_initial_locale(null, ["en", "fr", "de"]);
|
|
77
|
+
expect(result).toBe("en");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("get_initial_locale uses custom fallback locale when provided", () => {
|
|
81
|
+
const result = get_initial_locale("es", ["en", "fr", "de"], "de");
|
|
82
|
+
expect(result).toBe("de");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("i18n setup and initialization", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
vi.clearAllMocks();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
vi.resetAllMocks();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("load_translations adds messages for each language", () => {
|
|
96
|
+
const mockAddMessages = addMessages as unknown as ReturnType<
|
|
97
|
+
typeof vi.fn
|
|
98
|
+
>;
|
|
99
|
+
|
|
100
|
+
const custom_translations = {
|
|
101
|
+
en: {
|
|
102
|
+
greeting: "Hello"
|
|
103
|
+
},
|
|
104
|
+
fr: {
|
|
105
|
+
greeting: "Bonjour"
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
load_translations(custom_translations);
|
|
110
|
+
|
|
111
|
+
expect(mockAddMessages).toHaveBeenCalledTimes(2);
|
|
112
|
+
expect(mockAddMessages).toHaveBeenCalledWith(
|
|
113
|
+
"en",
|
|
114
|
+
custom_translations.en
|
|
115
|
+
);
|
|
116
|
+
expect(mockAddMessages).toHaveBeenCalledWith(
|
|
117
|
+
"fr",
|
|
118
|
+
custom_translations.fr
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("changeLocale sets the locale correctly", () => {
|
|
123
|
+
const mockSetLocale = locale.set as unknown as ReturnType<typeof vi.fn>;
|
|
124
|
+
|
|
125
|
+
changeLocale("fr");
|
|
126
|
+
|
|
127
|
+
expect(mockSetLocale).toHaveBeenCalledWith("fr");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("translation metadata handling", () => {
|
|
132
|
+
test("is_translation_metadata identifies valid metadata objects", () => {
|
|
133
|
+
expect(
|
|
134
|
+
is_translation_metadata({
|
|
135
|
+
__type__: "translation_metadata" as const,
|
|
136
|
+
key: "test.key"
|
|
137
|
+
})
|
|
138
|
+
).toBe(true);
|
|
139
|
+
expect(is_translation_metadata({ key: "test.key" })).toBe(false);
|
|
140
|
+
|
|
141
|
+
expect(Boolean(is_translation_metadata(null))).toBe(false);
|
|
142
|
+
expect(Boolean(is_translation_metadata(undefined))).toBe(false);
|
|
143
|
+
expect(Boolean(is_translation_metadata("not an object"))).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
27
146
|
});
|
package/src/i18n.ts
CHANGED
|
@@ -1,31 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
init,
|
|
4
|
-
getLocaleFromNavigator,
|
|
5
|
-
locale,
|
|
6
|
-
_
|
|
7
|
-
} from "svelte-i18n";
|
|
1
|
+
import { addMessages, init, getLocaleFromNavigator, locale } from "svelte-i18n";
|
|
2
|
+
import { formatter } from "./gradio_helper";
|
|
8
3
|
|
|
9
4
|
const langs = import.meta.glob("./lang/*.json", {
|
|
10
5
|
eager: true
|
|
11
6
|
});
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
export interface I18nData {
|
|
9
|
+
__type__: "translation_metadata";
|
|
10
|
+
key: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LangsRecord {
|
|
14
|
+
[lang: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function is_translation_metadata(obj: any): obj is I18nData {
|
|
18
|
+
console.log(obj);
|
|
19
|
+
const result =
|
|
20
|
+
obj &&
|
|
21
|
+
typeof obj === "object" &&
|
|
22
|
+
obj.__type__ === "translation_metadata" &&
|
|
23
|
+
typeof obj.key === "string";
|
|
24
|
+
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
|
|
29
|
+
export function translate_if_needed(value: any): string {
|
|
30
|
+
if (typeof value !== "string") {
|
|
31
|
+
return value;
|
|
17
32
|
}
|
|
18
|
-
>;
|
|
19
33
|
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
const i18n_marker = "__i18n__";
|
|
35
|
+
const marker_index = value.indexOf(i18n_marker);
|
|
36
|
+
|
|
37
|
+
if (marker_index === -1) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const before_marker =
|
|
43
|
+
marker_index > 0 ? value.substring(0, marker_index) : "";
|
|
44
|
+
|
|
45
|
+
const after_marker_index = marker_index + i18n_marker.length;
|
|
46
|
+
const json_start = value.indexOf("{", after_marker_index);
|
|
47
|
+
let json_end = -1;
|
|
48
|
+
let bracket_count = 0;
|
|
49
|
+
|
|
50
|
+
for (let i = json_start; i < value.length; i++) {
|
|
51
|
+
if (value[i] === "{") bracket_count++;
|
|
52
|
+
if (value[i] === "}") bracket_count--;
|
|
53
|
+
if (bracket_count === 0) {
|
|
54
|
+
json_end = i + 1;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (json_end === -1) {
|
|
60
|
+
console.error("Could not find end of JSON in i18n string");
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const metadata_json = value.substring(json_start, json_end);
|
|
65
|
+
const after_json = json_end < value.length ? value.substring(json_end) : "";
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const metadata = JSON.parse(metadata_json);
|
|
69
|
+
|
|
70
|
+
if (metadata && metadata.key) {
|
|
71
|
+
const translated = formatter(metadata.key);
|
|
72
|
+
return before_marker + translated + after_json;
|
|
73
|
+
}
|
|
74
|
+
} catch (jsonError) {
|
|
75
|
+
console.error("Error parsing i18n JSON:", jsonError);
|
|
76
|
+
}
|
|
22
77
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
78
|
+
return value;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error("Error processing translation:", e);
|
|
81
|
+
return value;
|
|
26
82
|
}
|
|
83
|
+
}
|
|
27
84
|
|
|
28
|
-
|
|
85
|
+
export function process_langs(): LangsRecord {
|
|
86
|
+
return Object.fromEntries(
|
|
87
|
+
Object.entries(langs).map(([path, module]) => [
|
|
88
|
+
path.split("/").pop()!.split(".")[0],
|
|
89
|
+
(module as Record<string, any>).default
|
|
90
|
+
])
|
|
91
|
+
);
|
|
29
92
|
}
|
|
30
93
|
|
|
31
94
|
const processed_langs = process_langs();
|
|
@@ -37,17 +100,26 @@ export const language_choices: [string, string][] = Object.entries(
|
|
|
37
100
|
|
|
38
101
|
export let all_common_keys: Set<string> = new Set();
|
|
39
102
|
|
|
40
|
-
for (const lang in processed_langs) {
|
|
41
|
-
addMessages(lang, processed_langs[lang]);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
103
|
let i18n_initialized = false;
|
|
104
|
+
let previous_translations: Record<string, Record<string, string>> | undefined;
|
|
105
|
+
|
|
106
|
+
export async function setupi18n(
|
|
107
|
+
custom_translations?: Record<string, Record<string, string>>
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
const should_reinitialize =
|
|
110
|
+
i18n_initialized && custom_translations !== previous_translations;
|
|
45
111
|
|
|
46
|
-
|
|
47
|
-
if (i18n_initialized) {
|
|
112
|
+
if (i18n_initialized && !should_reinitialize) {
|
|
48
113
|
return;
|
|
49
114
|
}
|
|
50
115
|
|
|
116
|
+
previous_translations = custom_translations;
|
|
117
|
+
|
|
118
|
+
load_translations({
|
|
119
|
+
...processed_langs,
|
|
120
|
+
...(custom_translations ?? {})
|
|
121
|
+
});
|
|
122
|
+
|
|
51
123
|
const browser_locale = getLocaleFromNavigator();
|
|
52
124
|
|
|
53
125
|
let initial_locale =
|
|
@@ -88,3 +160,33 @@ export async function setupi18n(): Promise<void> {
|
|
|
88
160
|
export function changeLocale(new_locale: string): void {
|
|
89
161
|
locale.set(new_locale);
|
|
90
162
|
}
|
|
163
|
+
|
|
164
|
+
export function get_initial_locale(
|
|
165
|
+
browser_locale: string | null,
|
|
166
|
+
available_locales: string[],
|
|
167
|
+
fallback_locale = "en"
|
|
168
|
+
): string {
|
|
169
|
+
if (!browser_locale) return fallback_locale;
|
|
170
|
+
|
|
171
|
+
if (available_locales.includes(browser_locale)) {
|
|
172
|
+
return browser_locale;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return fallback_locale;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function load_translations(
|
|
179
|
+
translations: LangsRecord | null | undefined
|
|
180
|
+
): void {
|
|
181
|
+
if (!translations) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
for (const lang in translations) {
|
|
187
|
+
addMessages(lang, translations[lang]);
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.error("Error loading translations:", e);
|
|
191
|
+
}
|
|
192
|
+
}
|