bcsfe 3.0.0__py3-none-any.whl
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.
- bcsfe/__init__.py +12 -0
- bcsfe/__main__.py +64 -0
- bcsfe/cli/__init__.py +21 -0
- bcsfe/cli/color.py +189 -0
- bcsfe/cli/dialog_creator.py +682 -0
- bcsfe/cli/edits/__init__.py +23 -0
- bcsfe/cli/edits/aku_realm.py +11 -0
- bcsfe/cli/edits/basic_items.py +402 -0
- bcsfe/cli/edits/cat_editor.py +715 -0
- bcsfe/cli/edits/clear_tutorial.py +11 -0
- bcsfe/cli/edits/enemy_editor.py +192 -0
- bcsfe/cli/edits/event_tickets.py +147 -0
- bcsfe/cli/edits/fixes.py +43 -0
- bcsfe/cli/edits/map.py +249 -0
- bcsfe/cli/edits/rare_ticket_trade.py +45 -0
- bcsfe/cli/feature_handler.py +294 -0
- bcsfe/cli/file_dialog.py +154 -0
- bcsfe/cli/main.py +327 -0
- bcsfe/cli/save_management.py +529 -0
- bcsfe/cli/server_cli.py +71 -0
- bcsfe/core/__init__.py +361 -0
- bcsfe/core/country_code.py +104 -0
- bcsfe/core/crypto.py +164 -0
- bcsfe/core/game/__init__.py +3 -0
- bcsfe/core/game/battle/__init__.py +3 -0
- bcsfe/core/game/battle/battle_items.py +127 -0
- bcsfe/core/game/battle/cleared_slots.py +306 -0
- bcsfe/core/game/battle/enemy.py +132 -0
- bcsfe/core/game/battle/slots.py +200 -0
- bcsfe/core/game/catbase/__init__.py +49 -0
- bcsfe/core/game/catbase/beacon_base.py +67 -0
- bcsfe/core/game/catbase/cat.py +1243 -0
- bcsfe/core/game/catbase/drop_chara.py +74 -0
- bcsfe/core/game/catbase/gatya.py +454 -0
- bcsfe/core/game/catbase/gatya_item.py +155 -0
- bcsfe/core/game/catbase/item_pack.py +190 -0
- bcsfe/core/game/catbase/login_bonuses.py +184 -0
- bcsfe/core/game/catbase/matatabi.py +69 -0
- bcsfe/core/game/catbase/medals.py +171 -0
- bcsfe/core/game/catbase/mission.py +321 -0
- bcsfe/core/game/catbase/my_sale.py +58 -0
- bcsfe/core/game/catbase/nyanko_club.py +254 -0
- bcsfe/core/game/catbase/officer_pass.py +79 -0
- bcsfe/core/game/catbase/playtime.py +97 -0
- bcsfe/core/game/catbase/powerup.py +179 -0
- bcsfe/core/game/catbase/scheme_items.py +195 -0
- bcsfe/core/game/catbase/special_skill.py +331 -0
- bcsfe/core/game/catbase/stamp.py +58 -0
- bcsfe/core/game/catbase/talent_orbs.py +710 -0
- bcsfe/core/game/catbase/unlock_popups.py +71 -0
- bcsfe/core/game/catbase/upgrade.py +190 -0
- bcsfe/core/game/catbase/user_rank_rewards.py +276 -0
- bcsfe/core/game/gamoto/__init__.py +9 -0
- bcsfe/core/game/gamoto/base_materials.py +90 -0
- bcsfe/core/game/gamoto/cat_shrine.py +207 -0
- bcsfe/core/game/gamoto/catamins.py +59 -0
- bcsfe/core/game/gamoto/gamatoto.py +496 -0
- bcsfe/core/game/gamoto/ototo.py +660 -0
- bcsfe/core/game/localizable.py +31 -0
- bcsfe/core/game/map/__init__.py +41 -0
- bcsfe/core/game/map/aku.py +229 -0
- bcsfe/core/game/map/challenge.py +73 -0
- bcsfe/core/game/map/chapters.py +362 -0
- bcsfe/core/game/map/dojo.py +360 -0
- bcsfe/core/game/map/enigma.py +219 -0
- bcsfe/core/game/map/event.py +898 -0
- bcsfe/core/game/map/ex_stage.py +102 -0
- bcsfe/core/game/map/gauntlets.py +351 -0
- bcsfe/core/game/map/item_reward_stage.py +416 -0
- bcsfe/core/game/map/legend_quest.py +381 -0
- bcsfe/core/game/map/map_names.py +100 -0
- bcsfe/core/game/map/map_reset.py +116 -0
- bcsfe/core/game/map/outbreaks.py +264 -0
- bcsfe/core/game/map/story.py +1166 -0
- bcsfe/core/game/map/timed_score.py +193 -0
- bcsfe/core/game/map/tower.py +68 -0
- bcsfe/core/game/map/uncanny.py +47 -0
- bcsfe/core/game/map/zero_legends.py +295 -0
- bcsfe/core/game_version.py +244 -0
- bcsfe/core/io/__init__.py +31 -0
- bcsfe/core/io/adb_handler.py +181 -0
- bcsfe/core/io/bc_csv.py +263 -0
- bcsfe/core/io/command.py +50 -0
- bcsfe/core/io/config.py +449 -0
- bcsfe/core/io/data.py +647 -0
- bcsfe/core/io/git_handler.py +92 -0
- bcsfe/core/io/json_file.py +45 -0
- bcsfe/core/io/path.py +269 -0
- bcsfe/core/io/root_handler.py +105 -0
- bcsfe/core/io/save.py +3730 -0
- bcsfe/core/io/thread_helper.py +76 -0
- bcsfe/core/io/waydroid.py +120 -0
- bcsfe/core/io/yaml.py +61 -0
- bcsfe/core/locale_handler.py +713 -0
- bcsfe/core/log.py +133 -0
- bcsfe/core/max_value_helper.py +35 -0
- bcsfe/core/server/__init__.py +21 -0
- bcsfe/core/server/client_info.py +34 -0
- bcsfe/core/server/event_data.py +377 -0
- bcsfe/core/server/game_data_getter.py +217 -0
- bcsfe/core/server/headers.py +29 -0
- bcsfe/core/server/managed_item.py +203 -0
- bcsfe/core/server/request.py +64 -0
- bcsfe/core/server/server_handler.py +668 -0
- bcsfe/core/server/updater.py +86 -0
- bcsfe/core/theme_handler.py +308 -0
- bcsfe/files/locales/en/core/config.properties +94 -0
- bcsfe/files/locales/en/core/files.properties +9 -0
- bcsfe/files/locales/en/core/input.properties +29 -0
- bcsfe/files/locales/en/core/locale.properties +33 -0
- bcsfe/files/locales/en/core/main.properties +108 -0
- bcsfe/files/locales/en/core/save.properties +97 -0
- bcsfe/files/locales/en/core/server.properties +47 -0
- bcsfe/files/locales/en/core/theme.properties +24 -0
- bcsfe/files/locales/en/core/updater.properties +19 -0
- bcsfe/files/locales/en/edits/bannable_items.properties +30 -0
- bcsfe/files/locales/en/edits/cats.properties +118 -0
- bcsfe/files/locales/en/edits/enemy.properties +17 -0
- bcsfe/files/locales/en/edits/fixes.properties +13 -0
- bcsfe/files/locales/en/edits/gamototo.properties +54 -0
- bcsfe/files/locales/en/edits/gatya.properties +5 -0
- bcsfe/files/locales/en/edits/gold_pass.properties +6 -0
- bcsfe/files/locales/en/edits/items.properties +52 -0
- bcsfe/files/locales/en/edits/map.properties +122 -0
- bcsfe/files/locales/en/edits/medals.properties +8 -0
- bcsfe/files/locales/en/edits/missions.properties +7 -0
- bcsfe/files/locales/en/edits/playtime.properties +7 -0
- bcsfe/files/locales/en/edits/scheme_items.properties +6 -0
- bcsfe/files/locales/en/edits/special_skills.properties +10 -0
- bcsfe/files/locales/en/edits/talent_orbs.properties +26 -0
- bcsfe/files/locales/en/edits/treasures.properties +24 -0
- bcsfe/files/locales/en/edits/user_rank.properties +9 -0
- bcsfe/files/locales/vi/core/config.properties +84 -0
- bcsfe/files/locales/vi/core/files.properties +9 -0
- bcsfe/files/locales/vi/core/input.properties +28 -0
- bcsfe/files/locales/vi/core/locale.properties +29 -0
- bcsfe/files/locales/vi/core/main.properties +108 -0
- bcsfe/files/locales/vi/core/save.properties +99 -0
- bcsfe/files/locales/vi/core/server.properties +67 -0
- bcsfe/files/locales/vi/core/theme.properties +23 -0
- bcsfe/files/locales/vi/core/updater.properties +21 -0
- bcsfe/files/locales/vi/edits/bannable_items.properties +30 -0
- bcsfe/files/locales/vi/edits/cats.properties +125 -0
- bcsfe/files/locales/vi/edits/enemy.properties +17 -0
- bcsfe/files/locales/vi/edits/fixes.properties +13 -0
- bcsfe/files/locales/vi/edits/gamototo.properties +54 -0
- bcsfe/files/locales/vi/edits/gatya.properties +4 -0
- bcsfe/files/locales/vi/edits/gold_pass.properties +6 -0
- bcsfe/files/locales/vi/edits/items.properties +51 -0
- bcsfe/files/locales/vi/edits/map.properties +118 -0
- bcsfe/files/locales/vi/edits/medals.properties +8 -0
- bcsfe/files/locales/vi/edits/missions.properties +7 -0
- bcsfe/files/locales/vi/edits/playtime.properties +7 -0
- bcsfe/files/locales/vi/edits/scheme_items.properties +6 -0
- bcsfe/files/locales/vi/edits/special_skills.properties +10 -0
- bcsfe/files/locales/vi/edits/talent_orbs.properties +25 -0
- bcsfe/files/locales/vi/edits/treasures.properties +24 -0
- bcsfe/files/locales/vi/edits/user_rank.properties +9 -0
- bcsfe/files/locales/vi/metadata.json +4 -0
- bcsfe/files/max_values.json +26 -0
- bcsfe/files/themes/default.json +16 -0
- bcsfe/py.typed +0 -0
- bcsfe-3.0.0.dist-info/METADATA +393 -0
- bcsfe-3.0.0.dist-info/RECORD +168 -0
- bcsfe-3.0.0.dist-info/WHEEL +5 -0
- bcsfe-3.0.0.dist-info/entry_points.txt +2 -0
- bcsfe-3.0.0.dist-info/licenses/LICENSE +675 -0
- bcsfe-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import dataclasses
|
|
3
|
+
import tempfile
|
|
4
|
+
from typing import Any
|
|
5
|
+
from bcsfe import core
|
|
6
|
+
from bcsfe.cli import color
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PropertySet:
|
|
10
|
+
"""Represents a set of properties in a property file."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, locale: str, property: str):
|
|
13
|
+
"""Initializes a new instance of the PropertySet class.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
locale (str): Language code of the locale.
|
|
17
|
+
property (str): Name of the property file.
|
|
18
|
+
"""
|
|
19
|
+
self.locale = locale
|
|
20
|
+
self.property = property
|
|
21
|
+
self.path = LocalManager.get_locale_folder(locale).add(property + ".properties")
|
|
22
|
+
self.properties: dict[str, tuple[str, str]] = {}
|
|
23
|
+
self.parse()
|
|
24
|
+
|
|
25
|
+
def parse(self):
|
|
26
|
+
"""Parses the property file.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
KeyError: If a key is already defined in the property file.
|
|
30
|
+
"""
|
|
31
|
+
lines = self.path.read().to_str().splitlines()
|
|
32
|
+
i = 0
|
|
33
|
+
in_multi_line = False
|
|
34
|
+
multi_line_text = ""
|
|
35
|
+
multi_line_key = ""
|
|
36
|
+
|
|
37
|
+
while i < len(lines):
|
|
38
|
+
line = lines[i]
|
|
39
|
+
finish_multiline = False
|
|
40
|
+
if (in_multi_line and not line.startswith(">")) or (
|
|
41
|
+
in_multi_line and i == len(lines) - 1
|
|
42
|
+
):
|
|
43
|
+
in_multi_line = False
|
|
44
|
+
finish_multiline = True
|
|
45
|
+
if multi_line_key in self.properties:
|
|
46
|
+
raise KeyError(
|
|
47
|
+
f"Key {multi_line_key} already exists in property file"
|
|
48
|
+
)
|
|
49
|
+
if line.startswith(">"):
|
|
50
|
+
multi_line_text += line[1:]
|
|
51
|
+
else:
|
|
52
|
+
multi_line_text = multi_line_text[:-1] # remove extra newline
|
|
53
|
+
self.properties[multi_line_key] = (multi_line_text, self.property)
|
|
54
|
+
multi_line_text = ""
|
|
55
|
+
multi_line_key = ""
|
|
56
|
+
if line.startswith("#") or not line:
|
|
57
|
+
i += 1
|
|
58
|
+
continue
|
|
59
|
+
if line.startswith(">") and in_multi_line:
|
|
60
|
+
multi_line_text += line[1:] + "\n"
|
|
61
|
+
|
|
62
|
+
parts = line.split("=")
|
|
63
|
+
if line.strip().endswith("="):
|
|
64
|
+
in_multi_line = True
|
|
65
|
+
multi_line_key = parts[0]
|
|
66
|
+
|
|
67
|
+
if not in_multi_line and not finish_multiline:
|
|
68
|
+
key = parts[0]
|
|
69
|
+
value = "=".join(parts[1:])
|
|
70
|
+
if key in self.properties:
|
|
71
|
+
raise KeyError(f"Key {key} already exists in property file")
|
|
72
|
+
self.properties[key] = (value, self.property)
|
|
73
|
+
|
|
74
|
+
i += 1
|
|
75
|
+
|
|
76
|
+
def get_key(self, key: str) -> str:
|
|
77
|
+
"""Gets a key from the property file.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
key (str): Key to get.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
str: Value of the key.
|
|
84
|
+
"""
|
|
85
|
+
return (
|
|
86
|
+
self.properties.get(key, key)[0].replace("\\n", "\n").replace("\\t", "\t")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def from_config(property: str) -> PropertySet:
|
|
91
|
+
"""Gets a PropertySet from the language code in the config.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
property (str): Name of the property file.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
PropertySet: PropertySet for the property file.
|
|
98
|
+
"""
|
|
99
|
+
return PropertySet(
|
|
100
|
+
core.core_data.config.get_str(core.ConfigKey.LOCALE), property
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class LocalManager:
|
|
105
|
+
"""Manages properties for a locale"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, locale: str | None = None):
|
|
108
|
+
"""Initializes a new instance of the LocalManager class.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
locale (str): Language code of the locale.
|
|
112
|
+
"""
|
|
113
|
+
if locale is None:
|
|
114
|
+
lc = core.core_data.config.get_str(core.ConfigKey.LOCALE)
|
|
115
|
+
else:
|
|
116
|
+
lc = locale
|
|
117
|
+
|
|
118
|
+
self.locale = lc
|
|
119
|
+
self.path = LocalManager.get_locale_folder(lc)
|
|
120
|
+
self.properties: dict[str, PropertySet] = {}
|
|
121
|
+
self.all_properties: dict[str, tuple[str, str]] = {}
|
|
122
|
+
self.en_properties: dict[str, tuple[str, str]] = {}
|
|
123
|
+
self.en_properties_path = LocalManager.get_locale_folder("en")
|
|
124
|
+
self.authors: list[str] = ["fieryhenry"]
|
|
125
|
+
self.name: str = "English"
|
|
126
|
+
self.parse()
|
|
127
|
+
if self.locale == "en":
|
|
128
|
+
self.en_properties = self.all_properties
|
|
129
|
+
|
|
130
|
+
if core.core_data.config.get_bool(core.ConfigKey.SHOW_MISSING_LOCALE_KEYS):
|
|
131
|
+
key = self.get_key("missing_locale_keys")
|
|
132
|
+
print(key)
|
|
133
|
+
print()
|
|
134
|
+
missing = self.get_missing_keys()
|
|
135
|
+
for key in missing:
|
|
136
|
+
print(key)
|
|
137
|
+
if not missing:
|
|
138
|
+
print(self.get_key("none"))
|
|
139
|
+
|
|
140
|
+
print()
|
|
141
|
+
|
|
142
|
+
key = self.get_key("extra_locale_keys")
|
|
143
|
+
print(key)
|
|
144
|
+
print()
|
|
145
|
+
extra = self.get_extra_keys()
|
|
146
|
+
for key in extra:
|
|
147
|
+
print(key)
|
|
148
|
+
if not extra:
|
|
149
|
+
print(self.get_key("none"))
|
|
150
|
+
|
|
151
|
+
print()
|
|
152
|
+
|
|
153
|
+
def get_missing_keys(self) -> list[tuple[str, str, str]]:
|
|
154
|
+
missing = set(self.en_properties.keys()) - set(self.all_properties.keys())
|
|
155
|
+
|
|
156
|
+
return [
|
|
157
|
+
(
|
|
158
|
+
key,
|
|
159
|
+
self.en_properties[key][0],
|
|
160
|
+
self.en_properties[key][1] + ".properties",
|
|
161
|
+
)
|
|
162
|
+
for key in missing
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
def get_extra_keys(self) -> list[tuple[str, str, str]]:
|
|
166
|
+
extra = set(self.all_properties.keys()) - set(self.en_properties.keys())
|
|
167
|
+
|
|
168
|
+
return [
|
|
169
|
+
(
|
|
170
|
+
key,
|
|
171
|
+
self.all_properties[key][0],
|
|
172
|
+
self.all_properties[key][1] + ".properties",
|
|
173
|
+
)
|
|
174
|
+
for key in extra
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
def parse(self):
|
|
178
|
+
"""Parses all property files in the locale folder recursively."""
|
|
179
|
+
for file in self.path.glob("**/*.properties", recursive=True):
|
|
180
|
+
file_name = file.strip_path_from(self.path).path
|
|
181
|
+
property_set = PropertySet(self.locale, file_name[:-11])
|
|
182
|
+
self.all_properties.update(property_set.properties)
|
|
183
|
+
self.properties[file_name[:-11]] = property_set
|
|
184
|
+
|
|
185
|
+
metadata_path = self.path.add("metadata.json")
|
|
186
|
+
|
|
187
|
+
if metadata_path.exists():
|
|
188
|
+
data = core.JsonFile.from_path(metadata_path)
|
|
189
|
+
self.authors = data.get("authors") or ["fieryhenry"]
|
|
190
|
+
self.name = data.get("name") or "English"
|
|
191
|
+
|
|
192
|
+
if self.locale != "en":
|
|
193
|
+
for file in self.en_properties_path.glob("**/*.properties", recursive=True):
|
|
194
|
+
file_name = file.strip_path_from(self.en_properties_path).path
|
|
195
|
+
property_set = PropertySet("en", file_name[:-11])
|
|
196
|
+
self.en_properties.update(property_set.properties)
|
|
197
|
+
|
|
198
|
+
def get_key(self, key: str, escape: bool = True, **kwargs: Any) -> str:
|
|
199
|
+
"""Gets a key from the property file.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
key (str): Key to get.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
str: Value of the key.
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
text = self.get_key_recursive(key, kwargs, escape)
|
|
209
|
+
except RecursionError:
|
|
210
|
+
text = key
|
|
211
|
+
|
|
212
|
+
for kwarg_key, kwarg_value in kwargs.items():
|
|
213
|
+
value = str(kwarg_value)
|
|
214
|
+
if escape:
|
|
215
|
+
value = LocalManager.escape_string(value)
|
|
216
|
+
text = text.replace("{" + kwarg_key + "}", value)
|
|
217
|
+
|
|
218
|
+
if "$(" in text:
|
|
219
|
+
text = self.parse_condition(text, kwargs)
|
|
220
|
+
|
|
221
|
+
return text
|
|
222
|
+
|
|
223
|
+
def parse_condition(self, text: str, kwargs: dict[str, Any]) -> str:
|
|
224
|
+
counter = 0
|
|
225
|
+
final_text = ""
|
|
226
|
+
in_expression = False
|
|
227
|
+
expression_text = ""
|
|
228
|
+
count_down = 0
|
|
229
|
+
while counter < len(text):
|
|
230
|
+
char = text[counter]
|
|
231
|
+
if counter == len(text) - 1:
|
|
232
|
+
final_text += char
|
|
233
|
+
break
|
|
234
|
+
next_char = text[counter + 1]
|
|
235
|
+
if char == "\\":
|
|
236
|
+
final_text += next_char
|
|
237
|
+
counter += 2
|
|
238
|
+
continue
|
|
239
|
+
if char == "$" and next_char == "(":
|
|
240
|
+
count_down = 0
|
|
241
|
+
in_expression = True
|
|
242
|
+
elif char == "/" and next_char == "$":
|
|
243
|
+
count_down = 2
|
|
244
|
+
in_expression = False
|
|
245
|
+
if len(expression_text) < 3:
|
|
246
|
+
counter += 1
|
|
247
|
+
continue
|
|
248
|
+
new_expression_text = expression_text[2:-1]
|
|
249
|
+
expression_text = ""
|
|
250
|
+
parts = new_expression_text.split(":")
|
|
251
|
+
if len(parts) < 2:
|
|
252
|
+
counter += 1
|
|
253
|
+
continue
|
|
254
|
+
keyword = parts[0].strip()
|
|
255
|
+
expression = parts[1].strip()
|
|
256
|
+
conditions = expression.split("$,")
|
|
257
|
+
string = ""
|
|
258
|
+
for i, condition in enumerate(conditions):
|
|
259
|
+
condition = condition.strip()
|
|
260
|
+
if not condition:
|
|
261
|
+
continue
|
|
262
|
+
if i == len(conditions) - 1:
|
|
263
|
+
string = condition
|
|
264
|
+
break
|
|
265
|
+
condition_parts = condition.split("($")
|
|
266
|
+
if len(condition_parts) < 2:
|
|
267
|
+
continue
|
|
268
|
+
logic = condition_parts[0].strip()
|
|
269
|
+
word = condition_parts[1].strip()
|
|
270
|
+
if not word:
|
|
271
|
+
continue
|
|
272
|
+
word = word[:-1]
|
|
273
|
+
value = kwargs.get(keyword)
|
|
274
|
+
if value is None:
|
|
275
|
+
continue
|
|
276
|
+
equality = None
|
|
277
|
+
if logic.startswith("=="):
|
|
278
|
+
equality = "=="
|
|
279
|
+
elif logic.startswith("!="):
|
|
280
|
+
equality = "!="
|
|
281
|
+
elif logic.startswith(">="):
|
|
282
|
+
equality = ">="
|
|
283
|
+
elif logic.startswith("<="):
|
|
284
|
+
equality = "<="
|
|
285
|
+
elif logic.startswith(">"):
|
|
286
|
+
equality = ">"
|
|
287
|
+
elif logic.startswith("<"):
|
|
288
|
+
equality = "<"
|
|
289
|
+
if equality is None:
|
|
290
|
+
continue
|
|
291
|
+
logic_parts = logic.split(equality)
|
|
292
|
+
if len(logic_parts) < 2:
|
|
293
|
+
continue
|
|
294
|
+
logic_value = logic_parts[1].strip()
|
|
295
|
+
|
|
296
|
+
if isinstance(value, int):
|
|
297
|
+
if not logic_value.isdigit():
|
|
298
|
+
continue
|
|
299
|
+
logic_value = int(logic_value)
|
|
300
|
+
|
|
301
|
+
if equality == "==":
|
|
302
|
+
if logic_value == value:
|
|
303
|
+
string = word
|
|
304
|
+
break
|
|
305
|
+
elif equality == "!=":
|
|
306
|
+
if logic_value != value:
|
|
307
|
+
string = word
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
if isinstance(logic_value, int) and not string:
|
|
311
|
+
if equality == ">":
|
|
312
|
+
if logic_value > value:
|
|
313
|
+
string = word
|
|
314
|
+
break
|
|
315
|
+
elif equality == ">=":
|
|
316
|
+
if logic_value >= value:
|
|
317
|
+
string = word
|
|
318
|
+
break
|
|
319
|
+
elif equality == "<":
|
|
320
|
+
if logic_value < value:
|
|
321
|
+
string = word
|
|
322
|
+
break
|
|
323
|
+
elif equality == "<=":
|
|
324
|
+
if logic_value <= value:
|
|
325
|
+
string = word
|
|
326
|
+
break
|
|
327
|
+
|
|
328
|
+
final_text += string
|
|
329
|
+
|
|
330
|
+
if in_expression:
|
|
331
|
+
expression_text += char
|
|
332
|
+
else:
|
|
333
|
+
if count_down <= 0:
|
|
334
|
+
final_text += char
|
|
335
|
+
else:
|
|
336
|
+
count_down -= 1
|
|
337
|
+
|
|
338
|
+
counter += 1
|
|
339
|
+
|
|
340
|
+
return final_text
|
|
341
|
+
|
|
342
|
+
@staticmethod
|
|
343
|
+
def get_special_chars() -> list[str]:
|
|
344
|
+
return ["<", ">", "/"]
|
|
345
|
+
|
|
346
|
+
@staticmethod
|
|
347
|
+
def escape_string(string: str) -> str:
|
|
348
|
+
for char in LocalManager.get_special_chars():
|
|
349
|
+
string = string.replace(char, "\\" + char)
|
|
350
|
+
return string
|
|
351
|
+
|
|
352
|
+
def get_key_recursive(
|
|
353
|
+
self,
|
|
354
|
+
key: str,
|
|
355
|
+
kwargs: dict[str, Any],
|
|
356
|
+
escape: bool = True,
|
|
357
|
+
) -> str:
|
|
358
|
+
value = self.all_properties.get(key)
|
|
359
|
+
if value is None:
|
|
360
|
+
value = self.en_properties.get(key, (key, key))
|
|
361
|
+
value = value[0].replace("\\n", "\n").replace("\\t", "\t")
|
|
362
|
+
# replace {{key}} with the value of the key
|
|
363
|
+
if "{{" not in value:
|
|
364
|
+
return value
|
|
365
|
+
char_index = 0
|
|
366
|
+
while char_index < len(value):
|
|
367
|
+
if value[char_index] == "{" and value[char_index + 1] == "{":
|
|
368
|
+
key_name = ""
|
|
369
|
+
char_index += 2
|
|
370
|
+
while value[char_index] != "}":
|
|
371
|
+
key_name += value[char_index]
|
|
372
|
+
char_index += 1
|
|
373
|
+
|
|
374
|
+
if key_name != key:
|
|
375
|
+
value = value.replace(
|
|
376
|
+
"{{" + key_name + "}}",
|
|
377
|
+
self.get_key(key_name, escape, **kwargs),
|
|
378
|
+
)
|
|
379
|
+
char_index += 1
|
|
380
|
+
|
|
381
|
+
return value
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def get_all_aliases(value: str) -> list[str]:
|
|
385
|
+
"""Gets all aliases from a string. Aliases are separated by |.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
value (str): String to get aliases from.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
list[str]: List of aliases.
|
|
392
|
+
"""
|
|
393
|
+
if "|" not in value:
|
|
394
|
+
return [value]
|
|
395
|
+
i = 0
|
|
396
|
+
aliases: list[str] = []
|
|
397
|
+
while i < len(value):
|
|
398
|
+
char = value[i]
|
|
399
|
+
prev_char = value[i - 1] if i > 0 else ""
|
|
400
|
+
if char == "|" and prev_char != "\\":
|
|
401
|
+
aliases.append(value[:i])
|
|
402
|
+
value = value[i + 1 :]
|
|
403
|
+
i = 0
|
|
404
|
+
i += 1
|
|
405
|
+
|
|
406
|
+
aliases.append(value)
|
|
407
|
+
return aliases
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def from_config() -> LocalManager:
|
|
411
|
+
"""Gets a LocalManager from the language code in the config.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
LocalManager: LocalManager for the locale.
|
|
415
|
+
"""
|
|
416
|
+
return LocalManager(core.core_data.config.get_str(core.ConfigKey.LOCALE))
|
|
417
|
+
|
|
418
|
+
def check_duplicates(self):
|
|
419
|
+
"""Checks for duplicate keys in all property files.
|
|
420
|
+
|
|
421
|
+
Raises:
|
|
422
|
+
KeyError: If a key is already defined in the property file.
|
|
423
|
+
"""
|
|
424
|
+
keys: set[str] = set()
|
|
425
|
+
for property in self.properties.values():
|
|
426
|
+
for key in property.properties.keys():
|
|
427
|
+
if key in keys:
|
|
428
|
+
raise KeyError(f"Duplicate key {key}")
|
|
429
|
+
keys.add(key)
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def get_all_locales() -> list[str]:
|
|
433
|
+
"""Gets all locales in the locales folder.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
list[str]: List of locales.
|
|
437
|
+
"""
|
|
438
|
+
locales: list[str] = []
|
|
439
|
+
for folder in LocalManager.get_locales_folder().get_dirs():
|
|
440
|
+
locales.append(folder.basename())
|
|
441
|
+
for folder in LocalManager.get_external_locales_folder().get_dirs():
|
|
442
|
+
locales.append(folder.basename())
|
|
443
|
+
return locales
|
|
444
|
+
|
|
445
|
+
@staticmethod
|
|
446
|
+
def get_locales_folder() -> core.Path:
|
|
447
|
+
"""Gets the locales folder.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
core.Path: Path to the locales folder.
|
|
451
|
+
"""
|
|
452
|
+
return core.Path("locales", True)
|
|
453
|
+
|
|
454
|
+
@staticmethod
|
|
455
|
+
def get_external_locales_folder() -> core.Path:
|
|
456
|
+
"""Gets the external locales folder.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
core.Path: Path to the external locales folder.
|
|
460
|
+
"""
|
|
461
|
+
return core.Path.get_documents_folder().add("external_locales")
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def get_locale_folder(locale: str) -> core.Path:
|
|
465
|
+
"""Gets the folder for a locale.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
locale (str): Language code of the locale.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
core.Path: Path to the locale folder.
|
|
472
|
+
"""
|
|
473
|
+
if locale.startswith("ext-"):
|
|
474
|
+
return LocalManager.get_external_locales_folder().add(locale)
|
|
475
|
+
return LocalManager.get_locales_folder().add(locale)
|
|
476
|
+
|
|
477
|
+
@staticmethod
|
|
478
|
+
def remove_locale(locale: str):
|
|
479
|
+
"""Removes a locale.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
locale (str): Language code of the locale.
|
|
483
|
+
"""
|
|
484
|
+
if locale not in LocalManager.get_all_locales():
|
|
485
|
+
return
|
|
486
|
+
if locale.startswith("ext-"):
|
|
487
|
+
extern = ExternalLocaleManager.get_external_locale(locale)
|
|
488
|
+
if extern is not None:
|
|
489
|
+
ExternalLocaleManager.delete_locale(extern)
|
|
490
|
+
LocalManager.get_external_locales_folder().add(locale).remove()
|
|
491
|
+
else:
|
|
492
|
+
LocalManager.get_locales_folder().add(locale).remove()
|
|
493
|
+
|
|
494
|
+
if core.core_data.config.get_str(core.ConfigKey.LOCALE) == locale:
|
|
495
|
+
core.core_data.config.set(core.ConfigKey.LOCALE, "en")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@dataclasses.dataclass
|
|
499
|
+
class ExternalLocale:
|
|
500
|
+
short_name: str
|
|
501
|
+
name: str
|
|
502
|
+
description: str
|
|
503
|
+
author: str
|
|
504
|
+
version: str
|
|
505
|
+
git_repo: str | None = None
|
|
506
|
+
|
|
507
|
+
def to_json(self) -> dict[str, Any]:
|
|
508
|
+
return dataclasses.asdict(self)
|
|
509
|
+
|
|
510
|
+
@staticmethod
|
|
511
|
+
def from_json(json_data: dict[str, Any]) -> ExternalLocale | None:
|
|
512
|
+
short_name = json_data.get("short_name")
|
|
513
|
+
name = json_data.get("name")
|
|
514
|
+
description = json_data.get("description")
|
|
515
|
+
author = json_data.get("author")
|
|
516
|
+
version = json_data.get("version")
|
|
517
|
+
git_repo = json_data.get("git_repo")
|
|
518
|
+
if (
|
|
519
|
+
short_name is None
|
|
520
|
+
or name is None
|
|
521
|
+
or description is None
|
|
522
|
+
or author is None
|
|
523
|
+
or version is None
|
|
524
|
+
):
|
|
525
|
+
return None
|
|
526
|
+
return ExternalLocale(
|
|
527
|
+
short_name,
|
|
528
|
+
name,
|
|
529
|
+
description,
|
|
530
|
+
author,
|
|
531
|
+
version,
|
|
532
|
+
git_repo,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
@staticmethod
|
|
536
|
+
def from_git_repo(git_repo: str) -> ExternalLocale | None:
|
|
537
|
+
repo = core.GitHandler().get_repo(git_repo)
|
|
538
|
+
if repo is None:
|
|
539
|
+
return None
|
|
540
|
+
locale_json = repo.get_file(core.Path("locale.json"))
|
|
541
|
+
if locale_json is None:
|
|
542
|
+
return None
|
|
543
|
+
json_data = core.JsonFile.from_data(locale_json).to_object()
|
|
544
|
+
json_data["git_repo"] = git_repo
|
|
545
|
+
return ExternalLocale.from_json(json_data)
|
|
546
|
+
|
|
547
|
+
def get_new_version(self) -> bool:
|
|
548
|
+
if self.git_repo is None:
|
|
549
|
+
return False
|
|
550
|
+
repo = core.GitHandler().get_repo(self.git_repo)
|
|
551
|
+
if repo is None:
|
|
552
|
+
return False
|
|
553
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
554
|
+
temp_dir = core.Path(tmp)
|
|
555
|
+
success = repo.clone_to_temp(temp_dir)
|
|
556
|
+
if not success:
|
|
557
|
+
return False
|
|
558
|
+
external_locale = ExternalLocaleManager.parse_external_locale(temp_dir)
|
|
559
|
+
if external_locale is None:
|
|
560
|
+
return False
|
|
561
|
+
version = external_locale.version
|
|
562
|
+
|
|
563
|
+
if version == self.version:
|
|
564
|
+
return False
|
|
565
|
+
|
|
566
|
+
self.name = external_locale.name
|
|
567
|
+
self.short_name = external_locale.short_name
|
|
568
|
+
self.description = external_locale.description
|
|
569
|
+
self.author = external_locale.author
|
|
570
|
+
self.version = version
|
|
571
|
+
|
|
572
|
+
success = repo.pull()
|
|
573
|
+
if not success:
|
|
574
|
+
return False
|
|
575
|
+
self.save()
|
|
576
|
+
return True
|
|
577
|
+
|
|
578
|
+
def save(self):
|
|
579
|
+
ExternalLocaleManager.save_locale(self)
|
|
580
|
+
|
|
581
|
+
def get_full_name(self) -> str:
|
|
582
|
+
return f"ext-{self.author}-{self.short_name}"
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class ExternalLocaleManager:
|
|
586
|
+
@staticmethod
|
|
587
|
+
def delete_locale(external_locale: ExternalLocale):
|
|
588
|
+
if external_locale.git_repo is None:
|
|
589
|
+
return
|
|
590
|
+
folder = core.GitHandler.get_repo_folder().add(
|
|
591
|
+
external_locale.git_repo.split("/")[-1]
|
|
592
|
+
)
|
|
593
|
+
folder.remove()
|
|
594
|
+
|
|
595
|
+
@staticmethod
|
|
596
|
+
def save_locale(
|
|
597
|
+
external_locale: ExternalLocale,
|
|
598
|
+
):
|
|
599
|
+
"""Saves an external locale.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
external_locale (ExternalLocale): External locale to save.
|
|
603
|
+
"""
|
|
604
|
+
if external_locale.git_repo is None:
|
|
605
|
+
return
|
|
606
|
+
folder = LocalManager.get_external_locales_folder().add(
|
|
607
|
+
external_locale.get_full_name()
|
|
608
|
+
)
|
|
609
|
+
folder.generate_dirs()
|
|
610
|
+
|
|
611
|
+
repo = core.GitHandler().get_repo(external_locale.git_repo)
|
|
612
|
+
if repo is None:
|
|
613
|
+
return
|
|
614
|
+
files_dir = repo.get_folder(core.Path("files"))
|
|
615
|
+
if files_dir is None:
|
|
616
|
+
return
|
|
617
|
+
|
|
618
|
+
files_dir.copy_tree(folder)
|
|
619
|
+
|
|
620
|
+
json_data = external_locale.to_json()
|
|
621
|
+
folder.add("locale.json").write(core.JsonFile.from_object(json_data).to_data())
|
|
622
|
+
|
|
623
|
+
@staticmethod
|
|
624
|
+
def parse_external_locale(path: core.Path) -> ExternalLocale | None:
|
|
625
|
+
"""Parses an external locale.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
path (core.Path): Path to the external locale.
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
ExternalLocale: External locale.
|
|
632
|
+
"""
|
|
633
|
+
if not path.exists():
|
|
634
|
+
return None
|
|
635
|
+
json_data = core.JsonFile.from_data(path.add("locale.json").read()).to_object()
|
|
636
|
+
return ExternalLocale.from_json(json_data)
|
|
637
|
+
|
|
638
|
+
@staticmethod
|
|
639
|
+
def update_external_locale(external_locale: ExternalLocale):
|
|
640
|
+
"""Updates an external locale.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
external_locale (ExternalLocale): External locale to update.
|
|
644
|
+
"""
|
|
645
|
+
if external_locale.git_repo is None:
|
|
646
|
+
return
|
|
647
|
+
color.ColoredText.localize(
|
|
648
|
+
"checking_for_locale_updates",
|
|
649
|
+
locale_name=external_locale.name,
|
|
650
|
+
)
|
|
651
|
+
updated = external_locale.get_new_version()
|
|
652
|
+
if updated:
|
|
653
|
+
color.ColoredText.localize(
|
|
654
|
+
"external_locale_updated",
|
|
655
|
+
locale_name=external_locale.name,
|
|
656
|
+
version=external_locale.version,
|
|
657
|
+
)
|
|
658
|
+
else:
|
|
659
|
+
color.ColoredText.localize(
|
|
660
|
+
"external_locale_no_update",
|
|
661
|
+
locale_name=external_locale.name,
|
|
662
|
+
version=external_locale.version,
|
|
663
|
+
)
|
|
664
|
+
print()
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
def update_all_external_locales(_: Any = None):
|
|
668
|
+
"""Updates all external locales."""
|
|
669
|
+
dirs = LocalManager.get_external_locales_folder().get_dirs()
|
|
670
|
+
if not dirs:
|
|
671
|
+
color.ColoredText.localize(
|
|
672
|
+
"no_external_locales",
|
|
673
|
+
)
|
|
674
|
+
return
|
|
675
|
+
if not core.GitHandler.is_git_installed():
|
|
676
|
+
color.ColoredText.localize(
|
|
677
|
+
"git_not_installed",
|
|
678
|
+
)
|
|
679
|
+
return
|
|
680
|
+
for folder in dirs:
|
|
681
|
+
locale = ExternalLocaleManager.parse_external_locale(folder)
|
|
682
|
+
if locale is None:
|
|
683
|
+
continue
|
|
684
|
+
ExternalLocaleManager.update_external_locale(locale)
|
|
685
|
+
|
|
686
|
+
@staticmethod
|
|
687
|
+
def get_external_locale_config() -> ExternalLocale | None:
|
|
688
|
+
"""Gets the external locale from the config.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
ExternalLocale: External locale.
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
locale = core.core_data.config.get_str(core.ConfigKey.LOCALE)
|
|
695
|
+
if not locale.startswith("ext-"):
|
|
696
|
+
return None
|
|
697
|
+
return ExternalLocaleManager.parse_external_locale(
|
|
698
|
+
LocalManager.get_locale_folder(locale)
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
@staticmethod
|
|
702
|
+
def get_external_locale(locale: str) -> ExternalLocale | None:
|
|
703
|
+
"""Gets the external locale from the code.
|
|
704
|
+
|
|
705
|
+
Returns:
|
|
706
|
+
ExternalLocale: External locale.
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
if not locale.startswith("ext-"):
|
|
710
|
+
return None
|
|
711
|
+
return ExternalLocaleManager.parse_external_locale(
|
|
712
|
+
LocalManager.get_locale_folder(locale)
|
|
713
|
+
)
|