Xinonria commited on
Commit
09ad14a
1 Parent(s): 6915670

fix some import bugs

Browse files
Files changed (3) hide show
  1. i18n/__init__.py +1 -0
  2. i18n/i18n.py +319 -0
  3. requirements.txt +4 -3
i18n/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .i18n import Translate, gettext
i18n/i18n.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This code is based on the gradio-i18n==0.0.10 project by hoveychen.
3
+ Original source: https://github.com/hoveychen/gradio-i18n
4
+ Modifications: When loading the app, force language switching and reload translations.
5
+ """
6
+
7
+ import functools
8
+ import inspect
9
+ import json
10
+ import os
11
+ from contextlib import contextmanager
12
+
13
+ import gradio as gr
14
+ import yaml
15
+ from gradio.blocks import Block, BlockContext, Context, LocalContext
16
+
17
+
18
+ # Monkey patch to escape I18nString type being stripped in gradio.Markdown
19
+ def escape_caller(func):
20
+ @functools.wraps(func)
21
+ def wrapper(*args, **kwargs):
22
+ if args and isinstance(args[0], I18nString):
23
+ return I18nString(func(*args, **kwargs))
24
+ return func(*args, **kwargs)
25
+
26
+ return wrapper
27
+
28
+
29
+ inspect.cleandoc = escape_caller(inspect.cleandoc)
30
+
31
+
32
+ class TranslateContext:
33
+ dictionary: dict = {}
34
+
35
+ def add_translation(translation: dict):
36
+ for k, v in translation.items():
37
+ if k not in TranslateContext.dictionary:
38
+ TranslateContext.dictionary[k] = {}
39
+ TranslateContext.dictionary[k].update(v)
40
+
41
+ lang_per_session = {}
42
+
43
+
44
+ def get_lang_from_request(request: gr.Request):
45
+ lang = request.headers["Accept-Language"].split(",")[0].split("-")[0].lower()
46
+ if not lang:
47
+ return "en"
48
+ return lang
49
+
50
+
51
+ class I18nString(str):
52
+ def __new__(cls, value):
53
+ request: gr.Request = LocalContext.request.get()
54
+ if request is None:
55
+ return super().__new__(cls, value)
56
+
57
+ lang = TranslateContext.lang_per_session.get(request.session_hash, "en")
58
+ result = TranslateContext.dictionary.get(lang, {}).get(value, value)
59
+ return result
60
+
61
+ def __str__(self):
62
+ request: gr.Request = LocalContext.request.get()
63
+ if request is None:
64
+ return self
65
+
66
+ lang = TranslateContext.lang_per_session.get(request.session_hash, "en")
67
+ result = TranslateContext.dictionary.get(lang, {}).get(self, super().__str__())
68
+ return result
69
+
70
+ def __add__(self, other):
71
+ v = str(self)
72
+ if isinstance(v, I18nString):
73
+ return super().__add__(other)
74
+ return v.__add__(other)
75
+
76
+ def __radd__(self, other):
77
+ v = str(self)
78
+ if isinstance(v, I18nString):
79
+ return other.__add__(other)
80
+ return other.__add__(v)
81
+
82
+ def __hash__(self) -> int:
83
+ return super().__hash__()
84
+
85
+ def format(self, *args, **kwargs) -> str:
86
+ v = str(self)
87
+ if isinstance(v, I18nString):
88
+ return super().format(*args, **kwargs)
89
+ return v.format(*args, **kwargs)
90
+
91
+ def unwrap(self):
92
+ return super().__str__()
93
+
94
+ @staticmethod
95
+ def unwrap_string(obj):
96
+ if isinstance(obj, I18nString):
97
+ return obj.unwrap()
98
+ return obj
99
+
100
+
101
+ def gettext(key: str):
102
+ """Wrapper text string to return I18nString
103
+ :param key: The key of the I18nString
104
+ """
105
+ return I18nString(key)
106
+
107
+
108
+ def iter_i18n_choices(choices):
109
+ """Iterate all I18nStrings in the choice, returns the indices of the I18nStrings"""
110
+ if not isinstance(choices, list) or len(choices) == 0:
111
+ return
112
+
113
+ if isinstance(choices[0], tuple):
114
+ for i, (k, v) in enumerate(choices):
115
+ if isinstance(k, I18nString):
116
+ yield i
117
+
118
+ else:
119
+ for i, v in enumerate(choices):
120
+ if isinstance(v, I18nString):
121
+ yield i
122
+
123
+
124
+ def iter_i18n_fields(component: gr.components.Component):
125
+ """Iterate all I18nStrings in the component"""
126
+ for name, value in inspect.getmembers(component):
127
+ if name == "value" and hasattr(component, "choices"):
128
+ # for those components with choices, the value will be kept as is
129
+ continue
130
+ if isinstance(value, I18nString):
131
+ yield name
132
+ elif name == "choices" and any(iter_i18n_choices(value)):
133
+ yield name
134
+
135
+
136
+ def iter_i18n_components(block: Block):
137
+ """Iterate all I18nStrings in the block"""
138
+ if isinstance(block, BlockContext):
139
+ for component in block.children:
140
+ for c in iter_i18n_components(component):
141
+ yield c
142
+
143
+ if any(iter_i18n_fields(block)):
144
+ yield block
145
+
146
+
147
+ def has_new_i18n_fields(block: Block, langs=["en"], existing_translation={}):
148
+ """Check if there are new I18nStrings in the block
149
+ :param block: The block to check
150
+ :param langs: The languages to check
151
+ :param existing_translation: The existing translation dictionary
152
+ :return: True if there are new I18nStrings, False otherwise
153
+ """
154
+ components = list(iter_i18n_components(block))
155
+
156
+ for lang in langs:
157
+ for component in components:
158
+ for field in iter_i18n_fields(component):
159
+ if field == "choices":
160
+ for idx in iter_i18n_choices(component.choices):
161
+ if isinstance(component.choices[idx], tuple):
162
+ value = component.choices[idx][0]
163
+ else:
164
+ value = component.choices[idx]
165
+ if value not in existing_translation.get(lang, {}):
166
+ return True
167
+ else:
168
+ value = getattr(component, field)
169
+ if value not in existing_translation.get(lang, {}):
170
+ return True
171
+
172
+ return False
173
+
174
+
175
+ def dump_blocks(block: Block, langs=["en"], include_translations={}):
176
+ """Dump all I18nStrings in the block to a dictionary
177
+ :param block: The block to dump
178
+ :param langs: The languages to dump
179
+ :param include_translations: The existing translation dictionary
180
+ :return: The dumped dictionary
181
+ """
182
+ components = list(iter_i18n_components(block))
183
+
184
+ def translate(lang, key):
185
+ return include_translations.get(lang, {}).get(key, key)
186
+
187
+ ret = {}
188
+
189
+ for lang in langs:
190
+ ret[lang] = {}
191
+ for component in components:
192
+ for field in iter_i18n_fields(component):
193
+ if field == "choices":
194
+ for idx in iter_i18n_choices(component.choices):
195
+ if isinstance(component.choices[idx], tuple):
196
+ value = component.choices[idx][0]
197
+ else:
198
+ value = component.choices[idx]
199
+ value = I18nString.unwrap_string(value)
200
+ ret[lang][value] = translate(lang, value)
201
+ else:
202
+ value = getattr(component, field)
203
+ value = I18nString.unwrap_string(value)
204
+ ret[lang][value] = translate(lang, value)
205
+
206
+ return ret
207
+
208
+
209
+ def translate_blocks(
210
+ block: gr.Blocks = None, translation={}, lang: gr.components.Component = None
211
+ ):
212
+ """Translate all I18nStrings in the block
213
+ :param block: The block to translate, default is the root block
214
+ :param translation: The translation dictionary
215
+ :param lang: The language component to change the language
216
+ """
217
+ if block is None:
218
+ block = Context.root_block
219
+
220
+ """Translate all I18nStrings in the block"""
221
+ if not isinstance(block, gr.Blocks):
222
+ raise ValueError("block must be an instance of gradio.Blocks")
223
+
224
+ components = list(iter_i18n_components(block))
225
+ TranslateContext.add_translation(translation)
226
+
227
+ def on_load(request: gr.Request):
228
+ return get_lang_from_request(request)
229
+
230
+ def on_lang_change(request: gr.Request, lang: str):
231
+ TranslateContext.lang_per_session[request.session_hash] = lang
232
+
233
+ outputs = []
234
+ for component in components:
235
+ fields = list(iter_i18n_fields(component))
236
+ if component == lang and "value" in fields:
237
+ raise ValueError("'lang' component can't has I18nStrings as value")
238
+
239
+ modified = {}
240
+
241
+ for field in fields:
242
+ if field == "choices":
243
+ choices = component.choices.copy()
244
+ for idx in iter_i18n_choices(choices):
245
+ if isinstance(choices[idx], tuple):
246
+ k, v = choices[idx]
247
+ choices[idx] = (str(k), I18nString.unwrap_string(v))
248
+ else:
249
+ v = choices[idx]
250
+ choices[idx] = (str(v), I18nString.unwrap_string(v))
251
+ modified[field] = choices
252
+ else:
253
+ modified[field] = str(getattr(component, field))
254
+
255
+ new_comp = gr.update(**modified)
256
+ outputs.append(new_comp)
257
+
258
+ if len(outputs) == 1:
259
+ return outputs[0]
260
+
261
+ return outputs
262
+
263
+ if lang is None:
264
+ lang = gr.State()
265
+
266
+ block.load(on_load, outputs=[lang]).then(on_lang_change, inputs=[lang], outputs=components)
267
+ lang.change(on_lang_change, inputs=[lang], outputs=components)
268
+
269
+
270
+ @contextmanager
271
+ def Translate(translation, lang: gr.components.Component = None, placeholder_langs=[]):
272
+ """Translate all I18nStrings in the block
273
+ :param translation: The translation dictionary or file path
274
+ :param lang: The language component to change the language
275
+ :param placeholder_langs: The placeholder languages to create a new translation file if translation is a file path
276
+ :return: The language component
277
+ """
278
+ if lang is None:
279
+ lang = gr.State()
280
+ yield lang
281
+
282
+ if isinstance(translation, dict):
283
+ # Static translation
284
+ translation_dict = translation
285
+ pass
286
+ elif isinstance(translation, str):
287
+ if os.path.exists(translation):
288
+ # Regard as a file path
289
+ with open(translation, "r") as f:
290
+ if translation.endswith(".json"):
291
+ translation_dict = json.load(f)
292
+ elif translation.endswith(".yaml"):
293
+ translation_dict = yaml.safe_load(f)
294
+ else:
295
+ raise ValueError("Unsupported file format")
296
+ else:
297
+ translation_dict = {}
298
+ else:
299
+ raise ValueError("Unsupported translation type")
300
+
301
+ block = Context.block
302
+ translate_blocks(block=block, translation=translation_dict, lang=lang)
303
+
304
+ if (
305
+ placeholder_langs
306
+ and isinstance(translation, str)
307
+ and has_new_i18n_fields(
308
+ block, langs=placeholder_langs, existing_translation=translation_dict
309
+ )
310
+ ):
311
+ merged = dump_blocks(
312
+ block, langs=placeholder_langs, include_translations=translation_dict
313
+ )
314
+
315
+ with open(translation, "w") as f:
316
+ if translation.endswith(".json"):
317
+ json.dump(merged, f, indent=2, ensure_ascii=False)
318
+ elif translation.endswith(".yaml"):
319
+ yaml.dump(merged, f, allow_unicode=True, sort_keys=False)
requirements.txt CHANGED
@@ -1,7 +1,8 @@
1
- gradio==4.42.0
2
  aiohttp
3
  pypinyin
4
- gradio-i18n==0.0.10
5
  pyloudnorm
6
  soundfile
7
- pydub
 
 
 
1
+ gradio
2
  aiohttp
3
  pypinyin
 
4
  pyloudnorm
5
  soundfile
6
+ pydub
7
+ python-dotenv
8
+ pymongo