def update_from_google_sheets(self, gsheets_manager, dev_language_file): # type: (GoogleSheetsManager, AndroidXmlFile) -> None pwt("UPDATING {}".format(self.original_file_path), color='y') online_translation_units = self.__get_google_sheets_translation_units(gsheets_manager=gsheets_manager, dev_language_file=dev_language_file) mismatched_records = [] for offline_t_unit in self.translation_units: online_t_unit = next((u for u in online_translation_units if u.identifier == offline_t_unit.identifier), None) if online_t_unit is None: mismatched_records.append(offline_t_unit) else: offline_target_text = offline_t_unit.target_text if offline_t_unit.target_text is not None else '' if online_t_unit.target_text != offline_target_text: offline_t_unit.target_text = online_t_unit.target_text mismatched_records.append(offline_t_unit) for untranslated_unit in self.untranslated: matched_unit = next((u for u in online_translation_units if u.identifier == untranslated_unit.identifier and u.target_text is not None and u.target_text != ''), None) if matched_unit is not None: m_unit_target_text = matched_unit.target_text if matched_unit.target_text is not None else '' untranslated_unit.target_text = m_unit_target_text mismatched_records.append(untranslated_unit) for t_unit in mismatched_records: matched_unit = next((online_unit for online_unit in online_translation_units if online_unit.identifier == t_unit.identifier), None) if matched_unit is not None and matched_unit.is_translated(): pwt(u"TRANSLATED: {}".format(matched_unit), color='g') t_unit.target_text = matched_unit.target_text
def load_xliff_files(languages, input_dir): """ Loads XLIFF files for the specified languages, from an input directory :param List[str] languages: a list of language codes to export localizations for :param str input_dir: the input directory path :return: a list of the XLIFF files found, loaded as IosXliffFile models :rtype: List[IosXliffFile] """ from os.path import join pwt('LOADING LOCALIZATIONS FROM {}'.format(input_dir), color='y') xliff_files = [] # type: List[IosXliffFile] for language in languages: xliff_file_path = join(input_dir, '{}.xcloc/'.format(language), 'Localized Contents', '{}.xliff'.format(language)) if not path.isfile(xliff_file_path): continue xliff_file = IosXliffFile(file_path=xliff_file_path) xliff_files.append(xliff_file) pwt('LOADED {}'.format(xliff_file_path), color='y') return xliff_files
def import_in_xcode(self, xcodeproj_path): """ Runs 'xcodebuild' to import the XLIFF file to the provided Xcode project :param str xcodeproj_path: """ import subprocess xcb_params = [ '-importLocalizations', '-localizationPath', self.original_file_path, '-project', xcodeproj_path ] pwt("IMPORTING {} INTO {}".format( self.original_file_path, path.basename(path.normpath(xcodeproj_path))), color='y') xcb = subprocess.Popen(['xcodebuild'] + xcb_params, stdout=subprocess.PIPE) xcb.wait()
def update_source_language(self, source_xml_file): """ Updates the source_text property, based on the provided source language file (matches between string IDs) :param AndroidXmlFile source_xml_file: the xml file for the development language """ pwt("UPDATING SOURCES FOR {}".format(self.original_file_path), color='y') # Add original text (source language) to translation units for t_unit in self.translation_units: t_unit_source_match = next( (t for t in source_xml_file.translation_units if t.identifier == t_unit.identifier), None) if t_unit_source_match is None: pwt("{} - {} NOT FOUND IN SOURCE LANGUAGE FILE".format( t_unit.identifier, t_unit.target_text), color='r') else: t_unit.source_text = t_unit_source_match.source_text target_lang_ids = [ t_unit.identifier for t_unit in self.translation_units ] self.untranslated = deepcopy([ t for t in source_xml_file.translation_units if t.identifier not in target_lang_ids ]) for t_unit in self.untranslated: t_unit.target_text = '' t_unit.target_language = self.target_language_code t_unit.friendly_target_language = self.target_language pwt("MISSING {} TRANSLATION FOR: {} - {}".format( self.target_language, t_unit.identifier, t_unit.source_text), color='r')
def import_from_res_folder(res_folder_path, development_language): # type: (str) -> List[AndroidXmlFile] pwt("LOADING XML FILES FROM {}".format(res_folder_path), color='y') xml_files = [] # type: List[AndroidXmlFile] for root, dirs, files in walk(res_folder_path): for file in files: # skip folders that are not for localized string if 'values' not in path.basename(root): continue # skip files that are not 'strings.xml' or 'array.xml' if not file.endswith('strings.xml'): # and not file.endswith('array.xml'): continue file_path = path.join(root, file) xml_files.append(AndroidXmlFile(file_path=file_path, source_language=development_language)) pwt("FOUND {}".format(file_path), color='y') if len(xml_files) == 0: pwt("COULD NOT FIND ANY XML FILES IN {}".format(res_folder_path), color='r') exit(1) source_language_file = [f for f in xml_files if f.source_language_code == f.target_language_code].pop() for xml_file in (f for f in xml_files if f.source_language_code != f.target_language_code): xml_file.update_source_language(source_xml_file=source_language_file) return xml_files
def upload_to_google_sheets(self, gsheets_manager): # type: (GoogleSheetsManager) -> None pwt("SYNCING {} WITH GOOGLE SHEETS".format(self.original_file_path), color='y') lang_ws = gsheets_manager.get_worksheet(platform='android', language=self.target_language, header_values=self.header_values) ws_records = lang_ws.get_all_records(numericise_data=False, value_render=ValueRenderOption.FORMULA) ws_records_ids = [r[AndroidHeaderValues.STRING_ID] for r in ws_records] missing_records = [u.record_value for u in self.translation_units if u.identifier not in ws_records_ids] missing_untranslated_records = [u.record_value for u in self.untranslated if u.identifier not in ws_records_ids] missing_records += missing_untranslated_records if len(missing_records) > 0: lang_ws.insert_rows(row=lang_ws.rows, number=len(missing_records), values=missing_records, inherit=True) lang_ws.sort_range((2, 1), (lang_ws.rows, lang_ws.cols)) for r_to_add in missing_records: pwt("ADDED {} TO {} - {}".format(r_to_add, lang_ws.spreadsheet.title, lang_ws.title), color='y') pwt("ADDED {} RECORDS TO {} - {}".format(len(missing_records), lang_ws.spreadsheet.title, lang_ws.title), color='g') pass
def update_from_google_sheets_memory(self, gsheets_manager): """ Updates its own properties (translation units) from the corresponding Google worksheet :param GoogleSheetsManager gsheets_manager: a GoogleSheetsManager instance that is authorized to make changes in the corresponding worksheet """ pwt("UPDATING {}".format(self.original_file_path), color='y') lang_ws = gsheets_manager.get_worksheet( platform='ios', language=self.target_language, header_values=self.header_values) # type: Worksheet ws_records = lang_ws.get_all_records( numericise_data=False, value_render=ValueRenderOption.UNFORMATTED_VALUE) ws_records_ids = [r[IosHeaderValues.KEY] for r in ws_records] online_translation_units = self.__get_google_sheets_translation_units( gsheets_manager=gsheets_manager ) # type: List[XliffTranslationUnit] untranslated_units = [ u for u in online_translation_units if u.is_translated() is False ] for untranslated_unit in untranslated_units: match = next((u for u in online_translation_units if u.source_text == untranslated_unit.source_text and u.is_translated()), None) if match is not None: for idx, record_id in enumerate(ws_records_ids): if record_id == untranslated_unit.identifier: target_cell_address = 'B{}'.format(idx + 2) untranslated_unit.target_text = match.target_text lang_ws.update_value(target_cell_address, match.target_text) pwt(u"TRANSLATED: {}".format(untranslated_unit), color='g') self.update_from_google_sheets(gsheets_manager=gsheets_manager)
def export_xliff_files(xcodeproj_path, languages, output_dir): """ Runs 'xcodebuild' to export localizations from the source Xcode project :param str xcodeproj_path: the path of the 'xcodeproj' file of the source project :param List[str] languages: a list of language codes to export localizations for :param str output_dir: path to a location where the XLIFF files will be exported :return: a list of the generated XLIFF files, loaded as IosXliffFile models :rtype: List[IosXliffFile] """ import subprocess from os.path import join xcb_params = [ '-exportLocalizations', '-localizationPath', output_dir, '-project', xcodeproj_path ] for language in languages: xcb_params.append('-exportLanguage') xcb_params.append(language) pwt('GENERATING XLIFF FILES FOR {} TO {}'.format(', '.join(languages), output_dir), color='y') xcb = subprocess.Popen(['xcodebuild'] + xcb_params, stdout=subprocess.PIPE) xcb.wait() xliff_files = [] # type: List[IosXliffFile] for language in languages: xliff_file_path = join(output_dir, '{}.xcloc/'.format(language), 'Localized Contents', '{}.xliff'.format(language)) xliff_file = IosXliffFile(file_path=xliff_file_path) xliff_files.append(xliff_file) return xliff_files
def update_from_google_sheets(self, gsheets_manager): """ Updates its own properties (translation units) from the corresponding Google worksheet :param GoogleSheetsManager gsheets_manager: a GoogleSheetsManager instance that is authorized to make changes in the corresponding worksheet """ pwt("UPDATING {}".format(self.original_file_path), color='y') online_translation_units = self.__get_google_sheets_translation_units( gsheets_manager=gsheets_manager) self.has_updates = False mismatched_records = [] for offline_t_unit in self.translation_units: online_t_units = [ u for u in online_translation_units if u.identifier == offline_t_unit.identifier ] if len(online_t_units) == 0: mismatched_records.append(offline_t_unit) elif online_t_units[0].target_text != offline_t_unit.target_text: offline_t_unit.target_text = online_t_units[0].target_text mismatched_records.append(offline_t_unit) for t_unit in mismatched_records: matched_units = [ online_unit for online_unit in online_translation_units if online_unit.identifier == t_unit.identifier ] if len(matched_units) > 0 and matched_units[0].is_translated(): pwt(u"TRANSLATED: {}".format(matched_units[0]), color='g') t_unit.target_text = matched_units[0].target_text self.has_updates = True self.update_source_xml()
default='en', help='development language code (default=en)', metavar='\b') return vars(ap.parse_args()) if __name__ == "__main__": args = parse_args() op_values = ['1', '2', '3'] op_type = get_input( 'Enter operation type [1=export, 2=import, 3=export&import]: ') if op_type not in op_values: pwt('INVALID OPERATION') exit(1) res_folder_path = args['res_folder_path'] service_account_file = args['auth_file_path'] user_email = args['email'] development_language = args['dev_language'] project_name = args['project_name'] google_sheets_manager = GoogleSheetsManager(service_account_file, user_email, project_name) android_files = import_from_res_folder(res_folder_path, development_language) development_language_file = next( (f for f in android_files
def sync_with_google_sheets(self, gsheets_manager, remove_unused_strings): """ Updates the corresponding worksheet with self.translation_units. It adds missing strings to the worksheet and updates any source text that has changed. This function does not remove any unused strings from Google Sheets. :param gsheets_manager: a GoogleSheetsManager instance that is authorized to make changes in the corresponding worksheet :type gsheets_manager: GoogleSheetsManager :rtype: None """ pwt("SYNCING {} WITH GOOGLE SHEETS".format(self.original_file_path), color='y') lang_ws = gsheets_manager.get_worksheet( platform='ios', language=self.target_language, header_values=self.header_values) # type: Worksheet ws_records = lang_ws.get_all_records( numericise_data=False, value_render=ValueRenderOption.FORMULA) ws_records_ids = [r[IosHeaderValues.KEY] for r in ws_records] records_to_add = [ u.record_value for u in self.translation_units if u.identifier not in ws_records_ids ] if len(records_to_add) > 0: lang_ws.insert_rows(row=lang_ws.rows, number=len(records_to_add), values=records_to_add, inherit=True) for idx, t_unit in enumerate(ws_records): match = next((u for u in self.translation_units if u.identifier == t_unit[IosHeaderValues.KEY]), None) if match is None: if remove_unused_strings: lang_ws.delete_rows(idx + 2) pwt('DELETED {} ({}) [FROM ROW {}]'.format( t_unit[IosHeaderValues.KEY], t_unit[self.source_language_header], idx + 2), color='r') continue if match.source_text != t_unit[self.source_language_header]: source_cell_address = 'A{}'.format(idx + 2) target_cell_address = 'B{}'.format(idx + 2) lang_ws.update_value(source_cell_address, match.source_text) lang_ws.update_value(target_cell_address, '') pwt('UPDATED SOURCE TEXT FOR {} FROM {} TO {}'.format( t_unit[IosHeaderValues.KEY], t_unit[self.source_language_header], match.source_text), color='g') lang_ws.sort_range((2, 1), (lang_ws.rows, lang_ws.cols)) for r_to_add in records_to_add: pwt("ADDED {} TO {} - {}".format(r_to_add, lang_ws.spreadsheet.title, lang_ws.title), color='g') pwt("ADDED {} RECORDS TO {} - {}".format(len(records_to_add), lang_ws.spreadsheet.title, lang_ws.title), color='g') pass
ap.add_argument('-o', '--output_dir', required=True, help='output dir for saving the xliff files generated from Xcode', metavar='\b') return vars(ap.parse_args()) if __name__ == "__main__": args = parse_args() op_type = get_input('Export XCLOC files? [0=no, 1=yes]: ') should_export = op_type == '1' op_values = ['1', '2', '3', '4'] op_type = get_input('Enter operation type [1=export, 2=import, 3=export&import, 4=remove unused]: ') if op_type not in op_values: pwt('INVALID OPERATION') exit(1) xcodeproj_path = args['xcodeproj_path'].rstrip('/') project_name = path.splitext(path.basename(xcodeproj_path))[0] loc_output_path = args['output_dir'] service_account_file = args['auth_file_path'] user_email = args['email'] localization_languages = args['languages'].split(',') # type: List[str] dev_language = args['dev_language'] google_sheets_manager = GoogleSheetsManager(service_account_file, user_email, project_name) # Starting with XCode 10.2, operations with the development languages (import/export) are supported if xcode_supports_dev_language_operations(): localization_languages = [dev_language] + localization_languages
return vars(ap.parse_args()) if __name__ == "__main__": args = parse_args() op_type = get_input('Export XCLOC files? [0=no, 1=yes]: ') should_export = op_type == '1' op_values = ['1', '2', '3', '4', '5'] op_type = get_input('Enter operation type [1=export, 2=import, 3=export&import, ' '4=remove unused, 5=translation memory]: ') if op_type not in op_values: pwt('INVALID OPERATION', color='r') exit(1) xcodeproj_path = args['xcodeproj_path'].rstrip('/') project_name = path.splitext(path.basename(xcodeproj_path))[0] loc_output_path = args['output_dir'] service_account_file = args['auth_file_path'] user_email = args['email'] localization_languages = args['languages'].split(',') # type: List[str] dev_language = args['dev_language'] google_sheets_manager = GoogleSheetsManager(service_account_file, user_email, project_name) # Starting with XCode 10.2, operations with the development languages (import/export) are supported if xcode_supports_dev_language_operations(): localization_languages = [dev_language] + lang_codes