mirror of
https://github.com/libretro/gambatte-libretro.git
synced 2024-11-26 17:30:23 +00:00
Fix Crowdin config & workflow (#245)
Also add new languages and update translation scripts
This commit is contained in:
parent
7e02df6004
commit
6ee43f79da
@ -1,14 +1,14 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
# 0: full struct; 1: up to & including first []; 2: content between first {}
|
# 0: full struct; 1: up to & including first []; 2 & 3: comments; 4: content between first {}
|
||||||
p_struct = re.compile(r'(struct\s*[a-zA-Z0-9_\s]+\[])\s*'
|
p_struct = re.compile(r'(\bstruct\b\s*[a-zA-Z0-9_\s]+\[])\s*' # 1st capturing group
|
||||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+)\s*)*'
|
r'(?:(?=(\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+))\2\s*)*' # 2nd capturing group
|
||||||
r'=\s*' # =
|
r'=\s*' # =
|
||||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+)\s*)*'
|
r'(?:(?=(\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+))\3\s*)*' # 3rd capturing group
|
||||||
r'{((?:.|[\r\n])*?)\{\s*NULL,\s*NULL,\s*NULL\s*(?:.|[\r\n])*?},?(?:.|[\r\n])*?};') # captures full struct, it's beginning and it's content
|
r'{((?:.|[\r\n])*?)\{\s*NULL,\s*NULL,\s*NULL\s*(?:.|[\r\n])*?},?(?:.|[\r\n])*?};') # captures full struct, it's beginning and it's content
|
||||||
# 0: type name[]; 1: type; 2: name
|
# 0: type name[]; 1: type; 2: name
|
||||||
p_type_name = re.compile(r'(retro_core_option_[a-zA-Z0-9_]+)\s*'
|
p_type_name = re.compile(r'(\bretro_core_option_[a-zA-Z0-9_]+)\s*'
|
||||||
r'(option_cats([a-z_]{0,8})|option_defs([a-z_]{0,8}))\s*\[]')
|
r'(\boption_cats([a-z_]{0,8})|\boption_defs([a-z_]*))\s*\[]')
|
||||||
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
||||||
p_option = re.compile(r'{\s*' # opening braces
|
p_option = re.compile(r'{\s*' # opening braces
|
||||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+|#.*[\r\n]+)\s*)*'
|
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+|#.*[\r\n]+)\s*)*'
|
||||||
@ -76,9 +76,9 @@ p_key_value = re.compile(r'{\s*' # opening braces
|
|||||||
|
|
||||||
p_masked = re.compile(r'([A-Z_][A-Z0-9_]+)\s*(\"(?:"\s*"|\\\s*|.)*\")')
|
p_masked = re.compile(r'([A-Z_][A-Z0-9_]+)\s*(\"(?:"\s*"|\\\s*|.)*\")')
|
||||||
|
|
||||||
p_intl = re.compile(r'(struct retro_core_option_definition \*option_defs_intl\[RETRO_LANGUAGE_LAST]) = {'
|
p_intl = re.compile(r'(\bstruct retro_core_option_definition \*option_defs_intl\[RETRO_LANGUAGE_LAST]) = {'
|
||||||
r'((?:.|[\r\n])*?)};')
|
r'((?:.|[\r\n])*?)};')
|
||||||
p_set = re.compile(r'static INLINE void libretro_set_core_options\(retro_environment_t environ_cb\)'
|
p_set = re.compile(r'\bstatic INLINE void libretro_set_core_options\(retro_environment_t environ_cb\)'
|
||||||
r'(?:.|[\r\n])*?};?\s*#ifdef __cplusplus\s*}\s*#endif')
|
r'(?:.|[\r\n])*?};?\s*#ifdef __cplusplus\s*}\s*#endif')
|
||||||
|
|
||||||
p_yaml = re.compile(r'"project_id": "[0-9]+".*\s*'
|
p_yaml = re.compile(r'"project_id": "[0-9]+".*\s*'
|
||||||
|
@ -134,13 +134,12 @@ def is_viable_non_dupe(text: str, comparison) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def is_viable_value(text: str) -> bool:
|
def is_viable_value(text: str) -> bool:
|
||||||
"""text must be longer than 2 ('""'), not 'NULL' and text.lower() not in
|
"""text must be longer than 2 ('""') and not 'NULL'.
|
||||||
{'"enabled"', '"disabled"', '"true"', '"false"', '"on"', '"off"'}.
|
|
||||||
|
|
||||||
:param text: String to be tested.
|
:param text: String to be tested.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
return 2 < len(text) and text != 'NULL' and text.lower() not in ON_OFFS
|
return 2 < len(text) and text != 'NULL'
|
||||||
|
|
||||||
|
|
||||||
def create_non_dupe(base_name: str, opt_num: int, comparison) -> str:
|
def create_non_dupe(base_name: str, opt_num: int, comparison) -> str:
|
||||||
@ -183,17 +182,17 @@ def get_texts(text: str) -> dict:
|
|||||||
if lang not in just_string:
|
if lang not in just_string:
|
||||||
hash_n_string[lang] = {}
|
hash_n_string[lang] = {}
|
||||||
just_string[lang] = set()
|
just_string[lang] = set()
|
||||||
|
is_v2_definition = 'retro_core_option_v2_definition' == struct_type_name[0]
|
||||||
is_v2 = False
|
|
||||||
pre_name = ''
|
pre_name = ''
|
||||||
|
# info texts format
|
||||||
p = cor.p_info
|
p = cor.p_info
|
||||||
if 'retro_core_option_v2_definition' == struct_type_name[0]:
|
if 'retro_core_option_v2_category' == struct_type_name[0]:
|
||||||
is_v2 = True
|
# prepend category labels, as they can be the same as option labels
|
||||||
elif 'retro_core_option_v2_category' == struct_type_name[0]:
|
|
||||||
pre_name = 'CATEGORY_'
|
pre_name = 'CATEGORY_'
|
||||||
|
# categories have different info texts format
|
||||||
p = cor.p_info_cat
|
p = cor.p_info_cat
|
||||||
|
|
||||||
struct_content = struct.group(2)
|
struct_content = struct.group(4)
|
||||||
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
||||||
struct_options = cor.p_option.finditer(struct_content)
|
struct_options = cor.p_option.finditer(struct_content)
|
||||||
for opt, option in enumerate(struct_options):
|
for opt, option in enumerate(struct_options):
|
||||||
@ -219,7 +218,7 @@ def get_texts(text: str) -> dict:
|
|||||||
if option.group(3):
|
if option.group(3):
|
||||||
infos = option.group(3)
|
infos = option.group(3)
|
||||||
option_info = p.finditer(infos)
|
option_info = p.finditer(infos)
|
||||||
if is_v2:
|
if is_v2_definition:
|
||||||
desc1 = next(option_info).group(1)
|
desc1 = next(option_info).group(1)
|
||||||
if is_viable_non_dupe(desc1, just_string[lang]):
|
if is_viable_non_dupe(desc1, just_string[lang]):
|
||||||
just_string[lang].add(desc1)
|
just_string[lang].add(desc1)
|
||||||
@ -248,16 +247,21 @@ def get_texts(text: str) -> dict:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f'Too few arguments in struct {struct_type_name[1]} option {option.group(1)}!')
|
raise ValueError(f'Too few arguments in struct {struct_type_name[1]} option {option.group(1)}!')
|
||||||
|
|
||||||
# group 4:
|
# group 4: key/value pairs
|
||||||
if option.group(4):
|
if option.group(4):
|
||||||
for j, kv_set in enumerate(cor.p_key_value.finditer(option.group(4))):
|
for j, kv_set in enumerate(cor.p_key_value.finditer(option.group(4))):
|
||||||
set_key, set_value = kv_set.group(1, 2)
|
set_key, set_value = kv_set.group(1, 2)
|
||||||
if not is_viable_value(set_value):
|
if not is_viable_value(set_value):
|
||||||
if not is_viable_value(set_key):
|
# use the key if value not available
|
||||||
continue
|
|
||||||
set_value = set_key
|
set_value = set_key
|
||||||
|
if not is_viable_value(set_value):
|
||||||
|
continue
|
||||||
# re.fullmatch(r'(?:[+-][0-9]+)+', value[1:-1])
|
# re.fullmatch(r'(?:[+-][0-9]+)+', value[1:-1])
|
||||||
if set_value not in just_string[lang] and not re.sub(r'[+-]', '', set_value[1:-1]).isdigit():
|
|
||||||
|
# add only if non-dupe, not translated by RetroArch directly & not purely numeric
|
||||||
|
if set_value not in just_string[lang]\
|
||||||
|
and set_value.lower() not in ON_OFFS\
|
||||||
|
and not re.sub(r'[+-]', '', set_value[1:-1]).isdigit():
|
||||||
clean_key = set_key[1:-1]
|
clean_key = set_key[1:-1]
|
||||||
clean_key = remove_special_chars(clean_key).upper().replace(' ', '_')
|
clean_key = remove_special_chars(clean_key).upper().replace(' ', '_')
|
||||||
m_h = create_non_dupe(re.sub(r'__+', '_', f"OPTION_VAL_{clean_key}"), opt, hash_n_string[lang])
|
m_h = create_non_dupe(re.sub(r'__+', '_', f"OPTION_VAL_{clean_key}"), opt, hash_n_string[lang])
|
||||||
@ -298,8 +302,12 @@ def h2json(file_paths: dict) -> dict:
|
|||||||
for file_lang in file_paths:
|
for file_lang in file_paths:
|
||||||
if not os.path.isfile(file_paths[file_lang]):
|
if not os.path.isfile(file_paths[file_lang]):
|
||||||
continue
|
continue
|
||||||
|
file_path = file_paths[file_lang]
|
||||||
jsons[file_lang] = file_paths[file_lang][:-2] + '.json'
|
try:
|
||||||
|
jsons[file_lang] = file_path[:file_path.rindex('.')] + '.json'
|
||||||
|
except ValueError:
|
||||||
|
print(f"File {file_path} has incorrect format! File ending missing?")
|
||||||
|
continue
|
||||||
|
|
||||||
p = cor.p_masked
|
p = cor.p_masked
|
||||||
|
|
||||||
@ -397,11 +405,11 @@ def get_crowdin_client(dir_path: str) -> str:
|
|||||||
return jar_path
|
return jar_path
|
||||||
|
|
||||||
|
|
||||||
def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str, file_path: str) -> None:
|
def create_intl_file(intl_file_path: str, localisations_path: str, text: str, file_path: str) -> None:
|
||||||
"""Creates 'libretro_core_options_intl.h' from Crowdin translations.
|
"""Creates 'libretro_core_options_intl.h' from Crowdin translations.
|
||||||
|
|
||||||
:param localisation_file_path: Path to 'libretro_core_options_intl.h'
|
:param intl_file_path: Path to 'libretro_core_options_intl.h'
|
||||||
:param intl_dir_path: Path to the intl/<core_name> directory.
|
:param localisations_path: Path to the intl/<core_name> directory.
|
||||||
:param text: Content of the 'libretro_core_options.h' being translated.
|
:param text: Content of the 'libretro_core_options.h' being translated.
|
||||||
:param file_path: Path to the '_us.h' file, containing the original English texts.
|
:param file_path: Path to the '_us.h' file, containing the original English texts.
|
||||||
:return: None
|
:return: None
|
||||||
@ -497,10 +505,11 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
'extern "C" {\n' \
|
'extern "C" {\n' \
|
||||||
'#endif\n'
|
'#endif\n'
|
||||||
|
|
||||||
if os.path.isfile(localisation_file_path):
|
if os.path.isfile(intl_file_path):
|
||||||
# copy top of the file for re-use
|
# copy top of the file for re-use
|
||||||
with open(localisation_file_path, 'r', encoding='utf-8') as intl: # libretro_core_options_intl.h
|
with open(intl_file_path, 'r', encoding='utf-8') as intl: # libretro_core_options_intl.h
|
||||||
in_text = intl.read()
|
in_text = intl.read()
|
||||||
|
# attempt 1: find the distinct comment header
|
||||||
intl_start = re.search(re.escape('/*\n'
|
intl_start = re.search(re.escape('/*\n'
|
||||||
' ********************************\n'
|
' ********************************\n'
|
||||||
' * Core Option Definitions\n'
|
' * Core Option Definitions\n'
|
||||||
@ -509,19 +518,22 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
if intl_start:
|
if intl_start:
|
||||||
out_txt = in_text[:intl_start.end(0)]
|
out_txt = in_text[:intl_start.end(0)]
|
||||||
else:
|
else:
|
||||||
|
# attempt 2: if no comment header present, find c++ compiler instruction (it is kind of a must)
|
||||||
intl_start = re.search(re.escape('#ifdef __cplusplus\n'
|
intl_start = re.search(re.escape('#ifdef __cplusplus\n'
|
||||||
'extern "C" {\n'
|
'extern "C" {\n'
|
||||||
'#endif\n'), in_text)
|
'#endif\n'), in_text)
|
||||||
if intl_start:
|
if intl_start:
|
||||||
out_txt = in_text[:intl_start.end(0)]
|
out_txt = in_text[:intl_start.end(0)]
|
||||||
|
# if all attempts fail, use default from above
|
||||||
|
|
||||||
# only write to file, if there is anything worthwhile to write!
|
# only write to file, if there is anything worthwhile to write!
|
||||||
overwrite = False
|
overwrite = False
|
||||||
|
|
||||||
# iterate through localisation files
|
# iterate through localisation files
|
||||||
files = {}
|
files = {}
|
||||||
for file in os.scandir(intl_dir_path):
|
for file in os.scandir(localisations_path):
|
||||||
files[file.name] = {'is_file': file.is_file(), 'path': file.path}
|
files[file.name] = {'is_file': file.is_file(), 'path': file.path}
|
||||||
|
|
||||||
for file in sorted(files): # intl/<core_name>/_*
|
for file in sorted(files): # intl/<core_name>/_*
|
||||||
if files[file]['is_file'] \
|
if files[file]['is_file'] \
|
||||||
and file.startswith('_') \
|
and file.startswith('_') \
|
||||||
@ -532,6 +544,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
struct_groups = cor.p_struct.finditer(text)
|
struct_groups = cor.p_struct.finditer(text)
|
||||||
lang_low = os.path.splitext(file)[0].lower()
|
lang_low = os.path.splitext(file)[0].lower()
|
||||||
lang_up = lang_low.upper()
|
lang_up = lang_low.upper()
|
||||||
|
# mark each language's section with a comment, for readability
|
||||||
out_txt = out_txt + f'/* RETRO_LANGUAGE{lang_up} */\n\n' # /* RETRO_LANGUAGE_NM */
|
out_txt = out_txt + f'/* RETRO_LANGUAGE{lang_up} */\n\n' # /* RETRO_LANGUAGE_NM */
|
||||||
|
|
||||||
# copy adjusted translations (makros)
|
# copy adjusted translations (makros)
|
||||||
@ -544,22 +557,22 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
if 3 > len(struct_type_name): # no language specifier
|
if 3 > len(struct_type_name): # no language specifier
|
||||||
new_decl = re.sub(re.escape(struct_type_name[1]), struct_type_name[1] + lang_low, declaration)
|
new_decl = re.sub(re.escape(struct_type_name[1]), struct_type_name[1] + lang_low, declaration)
|
||||||
else:
|
else:
|
||||||
new_decl = re.sub(re.escape(struct_type_name[2]), lang_low, declaration)
|
|
||||||
if '_us' != struct_type_name[2]:
|
if '_us' != struct_type_name[2]:
|
||||||
|
# only use _us constructs - other languages present in the source file are not important
|
||||||
continue
|
continue
|
||||||
|
new_decl = re.sub(re.escape(struct_type_name[2]), lang_low, declaration)
|
||||||
|
|
||||||
p = cor.p_info
|
p = (cor.p_info_cat if 'retro_core_option_v2_category' == struct_type_name[0] else cor.p_info)
|
||||||
if 'retro_core_option_v2_category' == struct_type_name[0]:
|
|
||||||
p = cor.p_info_cat
|
|
||||||
offset_construct = construct.start(0)
|
offset_construct = construct.start(0)
|
||||||
|
# append localised construct name and ' = {'
|
||||||
start = construct.end(1) - offset_construct
|
start = construct.end(1) - offset_construct
|
||||||
end = construct.start(2) - offset_construct
|
end = construct.start(4) - offset_construct
|
||||||
out_txt = out_txt + new_decl + construct.group(0)[start:end]
|
out_txt = out_txt + new_decl + construct.group(0)[start:end]
|
||||||
|
# insert macros
|
||||||
content = construct.group(2)
|
content = construct.group(4)
|
||||||
new_content = cor.p_option.sub(replace_option, content)
|
new_content = cor.p_option.sub(replace_option, content)
|
||||||
|
start = construct.end(4) - offset_construct
|
||||||
start = construct.end(2) - offset_construct
|
# append macro-filled content and close the construct
|
||||||
out_txt = out_txt + new_content + construct.group(0)[start:] + '\n'
|
out_txt = out_txt + new_content + construct.group(0)[start:] + '\n'
|
||||||
|
|
||||||
# for v2
|
# for v2
|
||||||
@ -574,7 +587,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
|
|
||||||
# only write to file, if there is anything worthwhile to write!
|
# only write to file, if there is anything worthwhile to write!
|
||||||
if overwrite:
|
if overwrite:
|
||||||
with open(localisation_file_path, 'w', encoding='utf-8') as intl:
|
with open(intl_file_path, 'w', encoding='utf-8') as intl:
|
||||||
intl.write(out_txt + '\n#ifdef __cplusplus\n'
|
intl.write(out_txt + '\n#ifdef __cplusplus\n'
|
||||||
'}\n#endif\n'
|
'}\n#endif\n'
|
||||||
'\n#endif')
|
'\n#endif')
|
||||||
@ -585,7 +598,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
if os.path.isfile(sys.argv[1]):
|
if os.path.isfile(sys.argv[1]) or sys.argv[1].endswith('.h'):
|
||||||
_temp = os.path.dirname(sys.argv[1])
|
_temp = os.path.dirname(sys.argv[1])
|
||||||
else:
|
else:
|
||||||
_temp = sys.argv[1]
|
_temp = sys.argv[1]
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
"files":
|
"files":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"source": "/intl/_core_name_/_us.json",
|
"source": "/_core_name_/_us.json",
|
||||||
"dest": "/_core_name_/core_options.json",
|
"dest": "/_core_name_/_core_name_.json",
|
||||||
"translation": "/intl/_core_name_/_%two_letters_code%.json",
|
"translation": "/_core_name_/_%two_letters_code%.json",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import core_option_translation as t
|
import core_option_translation as t
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
if t.os.path.isfile(t.sys.argv[1]):
|
if t.os.path.isfile(t.sys.argv[1]) or t.sys.argv[1].endswith('.h'):
|
||||||
_temp = t.os.path.dirname(t.sys.argv[1])
|
_temp = t.os.path.dirname(t.sys.argv[1])
|
||||||
else:
|
else:
|
||||||
_temp = t.sys.argv[1]
|
_temp = t.sys.argv[1]
|
||||||
|
@ -29,8 +29,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||||
f'"api_token": "{API_KEY}"',
|
f'"api_token": "{API_KEY}"',
|
||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
crowdin_config = re.sub(r'/_core_name_/',
|
crowdin_config = re.sub(r'/_core_name_',
|
||||||
f'/{CORE_NAME}/'
|
f'/{CORE_NAME}'
|
||||||
, crowdin_config)
|
, crowdin_config)
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
crowdin_config_file.write(crowdin_config)
|
crowdin_config_file.write(crowdin_config)
|
||||||
@ -68,8 +68,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
@ -84,8 +84,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import core_option_translation as t
|
import core_option_translation as t
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
if t.os.path.isfile(t.sys.argv[1]):
|
if t.os.path.isfile(t.sys.argv[1]) or t.sys.argv[1].endswith('.h'):
|
||||||
_temp = t.os.path.dirname(t.sys.argv[1])
|
_temp = t.os.path.dirname(t.sys.argv[1])
|
||||||
else:
|
else:
|
||||||
_temp = t.sys.argv[1]
|
_temp = t.sys.argv[1]
|
||||||
|
@ -29,8 +29,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||||
f'"api_token": "{API_KEY}"',
|
f'"api_token": "{API_KEY}"',
|
||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
crowdin_config = re.sub(r'/_core_name_/',
|
crowdin_config = re.sub(r'/_core_name_',
|
||||||
f'/{CORE_NAME}/'
|
f'/{CORE_NAME}'
|
||||||
, crowdin_config)
|
, crowdin_config)
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
crowdin_config_file.write(crowdin_config)
|
crowdin_config_file.write(crowdin_config)
|
||||||
@ -68,8 +68,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
@ -84,8 +84,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
|
@ -21,8 +21,8 @@ if __name__ == '__main__':
|
|||||||
print('Please provide Crowdin API Token and core name!')
|
print('Please provide Crowdin API Token and core name!')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
DIR_PATH = t.os.path.dirname(t.os.path.realpath(__file__))
|
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
YAML_PATH = t.os.path.join(DIR_PATH, 'crowdin.yaml')
|
YAML_PATH = os.path.join(DIR_PATH, 'crowdin.yaml')
|
||||||
|
|
||||||
# Apply Crowdin API Key
|
# Apply Crowdin API Key
|
||||||
with open(YAML_PATH, 'r') as crowdin_config_file:
|
with open(YAML_PATH, 'r') as crowdin_config_file:
|
||||||
@ -30,8 +30,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||||
f'"api_token": "{API_KEY}"',
|
f'"api_token": "{API_KEY}"',
|
||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
crowdin_config = re.sub(r'/_core_name_/',
|
crowdin_config = re.sub(r'/_core_name_',
|
||||||
f'/{CORE_NAME}/'
|
f'/{CORE_NAME}'
|
||||||
, crowdin_config)
|
, crowdin_config)
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
crowdin_config_file.write(crowdin_config)
|
crowdin_config_file.write(crowdin_config)
|
||||||
@ -39,22 +39,22 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
# Download Crowdin CLI
|
# Download Crowdin CLI
|
||||||
jar_name = 'crowdin-cli.jar'
|
jar_name = 'crowdin-cli.jar'
|
||||||
jar_path = t.os.path.join(DIR_PATH, jar_name)
|
jar_path = os.path.join(DIR_PATH, jar_name)
|
||||||
crowdin_cli_file = 'crowdin-cli.zip'
|
crowdin_cli_file = 'crowdin-cli.zip'
|
||||||
crowdin_cli_url = 'https://downloads.crowdin.com/cli/v3/' + crowdin_cli_file
|
crowdin_cli_url = 'https://downloads.crowdin.com/cli/v3/' + crowdin_cli_file
|
||||||
crowdin_cli_path = t.os.path.join(DIR_PATH, crowdin_cli_file)
|
crowdin_cli_path = os.path.join(DIR_PATH, crowdin_cli_file)
|
||||||
|
|
||||||
if not os.path.isfile(t.os.path.join(DIR_PATH, jar_name)):
|
if not os.path.isfile(os.path.join(DIR_PATH, jar_name)):
|
||||||
print('download crowdin-cli.jar')
|
print('download crowdin-cli.jar')
|
||||||
urllib.request.urlretrieve(crowdin_cli_url, crowdin_cli_path)
|
urllib.request.urlretrieve(crowdin_cli_url, crowdin_cli_path)
|
||||||
with zipfile.ZipFile(crowdin_cli_path, 'r') as zip_ref:
|
with zipfile.ZipFile(crowdin_cli_path, 'r') as zip_ref:
|
||||||
jar_dir = t.os.path.join(DIR_PATH, zip_ref.namelist()[0])
|
jar_dir = os.path.join(DIR_PATH, zip_ref.namelist()[0])
|
||||||
for file in zip_ref.namelist():
|
for file in zip_ref.namelist():
|
||||||
if file.endswith(jar_name):
|
if file.endswith(jar_name):
|
||||||
jar_file = file
|
jar_file = file
|
||||||
break
|
break
|
||||||
zip_ref.extract(jar_file, path=DIR_PATH)
|
zip_ref.extract(jar_file, path=DIR_PATH)
|
||||||
os.rename(t.os.path.join(DIR_PATH, jar_file), jar_path)
|
os.rename(os.path.join(DIR_PATH, jar_file), jar_path)
|
||||||
os.remove(crowdin_cli_path)
|
os.remove(crowdin_cli_path)
|
||||||
shutil.rmtree(jar_dir)
|
shutil.rmtree(jar_dir)
|
||||||
|
|
||||||
@ -74,24 +74,37 @@ if __name__ == '__main__':
|
|||||||
crowdin_config = re.sub(r'"api_token": ".*?"', '"api_token": "_secret_"', crowdin_config, 1)
|
crowdin_config = re.sub(r'"api_token": ".*?"', '"api_token": "_secret_"', crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
crowdin_config_file.write(crowdin_config)
|
crowdin_config_file.write(crowdin_config)
|
||||||
|
|
||||||
with open('intl/translation_workflow.py', 'r') as workflow:
|
with open('intl/upload_workflow.py', 'r') as workflow:
|
||||||
workflow_config = workflow.read()
|
workflow_config = workflow.read()
|
||||||
workflow_config = workflow_config.replace(
|
workflow_config = workflow_config.replace(
|
||||||
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||||
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||||
)
|
)
|
||||||
workflow_config = workflow_config.replace(
|
workflow_config = workflow_config.replace(
|
||||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])\n",
|
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_source_upload.py', api_key, core_name])"
|
||||||
|
)
|
||||||
|
with open('intl/upload_workflow.py', 'w') as workflow:
|
||||||
|
workflow.write(workflow_config)
|
||||||
|
|
||||||
|
with open('intl/download_workflow.py', 'r') as workflow:
|
||||||
|
workflow_config = workflow.read()
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||||
|
)
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||||
"subprocess.run(['python3', 'intl/crowdin_translation_download.py', api_key, core_name])"
|
"subprocess.run(['python3', 'intl/crowdin_translation_download.py', api_key, core_name])"
|
||||||
)
|
)
|
||||||
with open('intl/translation_workflow.py', 'w') as workflow:
|
with open('intl/download_workflow.py', 'w') as workflow:
|
||||||
workflow.write(workflow_config)
|
workflow.write(workflow_config)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -103,8 +116,8 @@ if __name__ == '__main__':
|
|||||||
crowdin_config, 1)
|
crowdin_config, 1)
|
||||||
|
|
||||||
# TODO this is NOT safe!
|
# TODO this is NOT safe!
|
||||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||||
'/_core_name_/',
|
'/_core_name_',
|
||||||
crowdin_config)
|
crowdin_config)
|
||||||
|
|
||||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||||
|
30
intl/remove_initial_cycle.py
Normal file
30
intl/remove_initial_cycle.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
with open('intl/upload_workflow.py', 'r') as workflow:
|
||||||
|
workflow_config = workflow.read()
|
||||||
|
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||||
|
)
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_source_upload.py', api_key, core_name])"
|
||||||
|
)
|
||||||
|
with open('intl/upload_workflow.py', 'w') as workflow:
|
||||||
|
workflow.write(workflow_config)
|
||||||
|
|
||||||
|
|
||||||
|
with open('intl/download_workflow.py', 'r') as workflow:
|
||||||
|
workflow_config = workflow.read()
|
||||||
|
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||||
|
)
|
||||||
|
workflow_config = workflow_config.replace(
|
||||||
|
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||||
|
"subprocess.run(['python3', 'intl/crowdin_translation_download.py', api_key, core_name])"
|
||||||
|
)
|
||||||
|
with open('intl/download_workflow.py', 'w') as workflow:
|
||||||
|
workflow.write(workflow_config)
|
@ -286,6 +286,11 @@ enum retro_language
|
|||||||
RETRO_LANGUAGE_INDONESIAN = 24,
|
RETRO_LANGUAGE_INDONESIAN = 24,
|
||||||
RETRO_LANGUAGE_SWEDISH = 25,
|
RETRO_LANGUAGE_SWEDISH = 25,
|
||||||
RETRO_LANGUAGE_UKRAINIAN = 26,
|
RETRO_LANGUAGE_UKRAINIAN = 26,
|
||||||
|
RETRO_LANGUAGE_CZECH = 27,
|
||||||
|
RETRO_LANGUAGE_CATALAN_VALENCIA = 28,
|
||||||
|
RETRO_LANGUAGE_CATALAN = 29,
|
||||||
|
RETRO_LANGUAGE_BRITISH_ENGLISH = 30,
|
||||||
|
RETRO_LANGUAGE_HUNGARIAN = 31,
|
||||||
RETRO_LANGUAGE_LAST,
|
RETRO_LANGUAGE_LAST,
|
||||||
|
|
||||||
/* Ensure sizeof(enum) == sizeof(int) */
|
/* Ensure sizeof(enum) == sizeof(int) */
|
||||||
@ -1756,6 +1761,12 @@ enum retro_mod
|
|||||||
* the frontend is attempting to call retro_run().
|
* the frontend is attempting to call retro_run().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT (72 | RETRO_ENVIRONMENT_EXPERIMENTAL)
|
||||||
|
/* int * --
|
||||||
|
* Tells the core about the context the frontend is asking for savestate.
|
||||||
|
* (see enum retro_savestate_context)
|
||||||
|
*/
|
||||||
|
|
||||||
/* VFS functionality */
|
/* VFS functionality */
|
||||||
|
|
||||||
/* File paths:
|
/* File paths:
|
||||||
@ -2993,6 +3004,35 @@ enum retro_pixel_format
|
|||||||
RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX
|
RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum retro_savestate_context
|
||||||
|
{
|
||||||
|
/* Standard savestate written to disk. */
|
||||||
|
RETRO_SAVESTATE_CONTEXT_NORMAL = 0,
|
||||||
|
|
||||||
|
/* Savestate where you are guaranteed that the same instance will load the save state.
|
||||||
|
* You can store internal pointers to code or data.
|
||||||
|
* It's still a full serialization and deserialization, and could be loaded or saved at any time.
|
||||||
|
* It won't be written to disk or sent over the network.
|
||||||
|
*/
|
||||||
|
RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE = 1,
|
||||||
|
|
||||||
|
/* Savestate where you are guaranteed that the same emulator binary will load that savestate.
|
||||||
|
* You can skip anything that would slow down saving or loading state but you can not store internal pointers.
|
||||||
|
* It won't be written to disk or sent over the network.
|
||||||
|
* Example: "Second Instance" runahead
|
||||||
|
*/
|
||||||
|
RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_BINARY = 2,
|
||||||
|
|
||||||
|
/* Savestate used within a rollback netplay feature.
|
||||||
|
* You should skip anything that would unnecessarily increase bandwidth usage.
|
||||||
|
* It won't be written to disk but it will be sent over the network.
|
||||||
|
*/
|
||||||
|
RETRO_SAVESTATE_CONTEXT_ROLLBACK_NETPLAY = 3,
|
||||||
|
|
||||||
|
/* Ensure sizeof() == sizeof(int). */
|
||||||
|
RETRO_SAVESTATE_CONTEXT_UNKNOWN = INT_MAX
|
||||||
|
};
|
||||||
|
|
||||||
struct retro_message
|
struct retro_message
|
||||||
{
|
{
|
||||||
const char *msg; /* Message to be displayed. */
|
const char *msg; /* Message to be displayed. */
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user