1
1
import os
2
- import json
3
2
import xml .etree .ElementTree as ET
4
3
import sys
5
4
import argparse
6
5
import re
7
6
from pathlib import Path
8
7
from colorama import Fore , Style
8
+ from generate_shared import load_glossary_dict , clean_string , setup_generation
9
9
10
10
# Variables that should be treated as numeric (using %d)
11
11
NUMERIC_VARIABLES = ['count' , 'found_count' , 'total_count' ]
12
12
13
+ AUTO_REPLACE_STATIC_STRINGS = False
14
+
13
15
14
16
# Parse command-line arguments
15
17
parser = argparse .ArgumentParser (description = 'Convert a XLIFF translation files to Android XML.' )
@@ -66,22 +68,8 @@ def repl(match):
66
68
67
69
return re .sub (r'\{([^}]+)\}' , repl , text )
68
70
69
- def clean_string (text ):
70
- # Note: any changes done for all platforms needs most likely to be done on crowdin side.
71
- # So we don't want to replace -> with → for instance, we want the crowdin strings to not have those at all.
72
- # We can use standard XML escaped characters for most things (since XLIFF is an XML format) but
73
- # want the following cases escaped in a particular way
74
- text = text .replace ("'" , r"\'" )
75
- text = text .replace (""" , "\" " )
76
- text = text .replace ("\" " , "\\ \" " )
77
- text = text .replace ("<b>" , "<b>" )
78
- text = text .replace ("</b>" , "</b>" )
79
- text = text .replace ("</br>" , "\\ n" )
80
- text = text .replace ("<br/>" , "\\ n" )
81
- text = text .replace ("&" , "&" ) # Assume any remaining ampersands are desired
82
- return text .strip () # Strip whitespace
83
-
84
- def generate_android_xml (translations , app_name ):
71
+
72
+ def generate_android_xml (translations , app_name , glossary_dict ):
85
73
sorted_translations = sorted (translations .items ())
86
74
result = '<?xml version="1.0" encoding="utf-8"?>\n '
87
75
result += '<resources>\n '
@@ -93,25 +81,26 @@ def generate_android_xml(translations, app_name):
93
81
if isinstance (target , dict ): # It's a plural group
94
82
result += f' <plurals name="{ resname } ">\n '
95
83
for form , value in target .items ():
96
- escaped_value = clean_string (convert_placeholders (value ))
84
+ escaped_value = clean_string (convert_placeholders (value ), True , glossary_dict , {} )
97
85
result += f' <item quantity="{ form } ">{ escaped_value } </item>\n '
98
86
result += ' </plurals>\n '
99
87
else : # It's a regular string (for these we DON'T want to convert the placeholders)
100
- escaped_target = clean_string (target )
88
+ escaped_target = clean_string (target , True , glossary_dict , {} )
101
89
result += f' <string name="{ resname } ">{ escaped_target } </string>\n '
102
90
103
91
result += '</resources>'
104
92
105
93
return result
106
94
107
- def convert_xliff_to_android_xml (input_file , output_dir , source_locale , locale , app_name ):
95
+ def convert_xliff_to_android_xml (input_file , output_dir , source_locale , locale , glossary_dict ):
108
96
if not os .path .exists (input_file ):
109
97
raise FileNotFoundError (f"Could not find '{ input_file } ' in raw translations directory" )
110
98
111
99
# Parse the XLIFF and convert to XML (only include the 'app_name' entry in the source language)
112
100
is_source_language = locale == source_locale
113
101
translations = parse_xliff (input_file )
114
- output_data = generate_android_xml (translations , app_name if is_source_language else None )
102
+ app_name = glossary_dict ['app_name' ]
103
+ output_data = generate_android_xml (translations , app_name if is_source_language else None , glossary_dict if AUTO_REPLACE_STATIC_STRINGS else {})
115
104
116
105
# android is pretty smart to resolve resources for translations, see the example here:
117
106
# https://developer.android.com/guide/topics/resources/multilingual-support#resource-resolution-examples
@@ -131,17 +120,10 @@ def convert_xliff_to_android_xml(input_file, output_dir, source_locale, locale,
131
120
132
121
133
122
def convert_non_translatable_strings_to_kotlin (input_file , output_path ):
134
- if not os .path .exists (input_file ):
135
- raise FileNotFoundError (f"Could not find '{ input_file } ' in raw translations directory" )
123
+ glossary_dict = load_glossary_dict (input_file )
136
124
137
- # Process the non-translatable string input
138
- non_translatable_strings_data = {}
139
- with open (input_file , 'r' , encoding = "utf-8" ) as file :
140
- non_translatable_strings_data = json .load (file )
141
-
142
- entries = non_translatable_strings_data ['data' ]
143
- max_key_length = max (len (entry ['data' ]['note' ].upper ()) for entry in entries )
144
- app_name = None
125
+ max_key_length = max (len (key ) for key in glossary_dict )
126
+ app_name = glossary_dict ['app_name' ]
145
127
146
128
# Output the file in the desired format
147
129
Path (output_path ).parent .mkdir (parents = True , exist_ok = True )
@@ -151,42 +133,24 @@ def convert_non_translatable_strings_to_kotlin(input_file, output_path):
151
133
file .write ('\n ' )
152
134
file .write ('// Non-translatable strings for use with the UI\n ' )
153
135
file .write ("object NonTranslatableStringConstants {\n " )
154
- for entry in entries :
155
- key = entry [ 'data' ][ 'note' ] .upper ()
156
- text = entry [ 'data' ][ 'text' ]
136
+ for key_lowercase in glossary_dict :
137
+ key = key_lowercase .upper ()
138
+ text = glossary_dict [ key_lowercase ]
157
139
file .write (f' const val { key :<{max_key_length }} = "{ text } "\n ' )
158
140
159
- if key == 'APP_NAME' :
160
- app_name = text
161
-
162
141
file .write ('}\n ' )
163
142
file .write ('\n ' )
164
143
165
- return app_name
166
-
167
- def convert_all_files (input_directory ):
168
- # Extract the project information
169
- print (f"\033 [2K{ Fore .WHITE } ⏳ Processing project info...{ Style .RESET_ALL } " , end = '\r ' )
170
- project_info_file = os .path .join (input_directory , "_project_info.json" )
171
- if not os .path .exists (project_info_file ):
172
- raise FileNotFoundError (f"Could not find '{ project_info_file } ' in raw translations directory" )
173
-
174
- project_details = {}
175
- with open (project_info_file , 'r' , encoding = "utf-8" ) as file :
176
- project_details = json .load (file )
177
-
178
- # Extract the language info and sort the target languages alphabetically by locale
179
- source_language = project_details ['data' ]['sourceLanguage' ]
180
- target_languages = project_details ['data' ]['targetLanguages' ]
181
- target_languages .sort (key = lambda x : x ['locale' ])
182
- num_languages = len (target_languages )
183
- print (f"\033 [2K{ Fore .GREEN } ✅ Project info processed, { num_languages } languages will be converted{ Style .RESET_ALL } " )
184
-
185
- # Convert the non-translatable strings to the desired format
186
- print (f"\033 [2K{ Fore .WHITE } ⏳ Generating static strings file...{ Style .RESET_ALL } " , end = '\r ' )
187
- non_translatable_strings_file = os .path .join (input_directory , "_non_translatable_strings.json" )
188
- app_name = convert_non_translatable_strings_to_kotlin (non_translatable_strings_file , NON_TRANSLATABLE_STRINGS_OUTPUT_PATH )
144
+ if not app_name :
145
+ raise ValueError ("could not find app_name in glossary_dict" )
146
+
147
+ def convert_all_files (input_directory : str ):
148
+ setup_values = setup_generation (input_directory )
149
+ source_language , rtl_languages , non_translatable_strings_file , target_languages = setup_values .values ()
150
+
151
+ convert_non_translatable_strings_to_kotlin (non_translatable_strings_file , NON_TRANSLATABLE_STRINGS_OUTPUT_PATH )
189
152
print (f"\033 [2K{ Fore .GREEN } ✅ Static string generation complete{ Style .RESET_ALL } " )
153
+ glossary_dict = load_glossary_dict (non_translatable_strings_file )
190
154
191
155
# Convert the XLIFF data to the desired format
192
156
print (f"\033 [2K{ Fore .WHITE } ⏳ Converting translations to target format...{ Style .RESET_ALL } " , end = '\r ' )
@@ -199,7 +163,7 @@ def convert_all_files(input_directory):
199
163
continue
200
164
print (f"\033 [2K{ Fore .WHITE } ⏳ Converting translations for { lang_locale } to target format...{ Style .RESET_ALL } " , end = '\r ' )
201
165
input_file = os .path .join (input_directory , f"{ lang_locale } .xliff" )
202
- convert_xliff_to_android_xml (input_file , TRANSLATIONS_OUTPUT_DIRECTORY , source_locale , lang_locale , app_name )
166
+ convert_xliff_to_android_xml (input_file , TRANSLATIONS_OUTPUT_DIRECTORY , source_locale , lang_locale , glossary_dict )
203
167
print (f"\033 [2K{ Fore .GREEN } ✅ All conversions complete{ Style .RESET_ALL } " )
204
168
205
169
if __name__ == "__main__" :
0 commit comments