Example #1
0
    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
Example #2
0
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
Example #3
0
    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')
Example #5
0
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
Example #6
0
    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
Example #7
0
    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)
Example #8
0
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
Example #9
0
    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
Example #11
0
    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
Example #12
0
    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