You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			258 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			258 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
#!/bin/python3
 | 
						|
 | 
						|
import json
 | 
						|
import sys
 | 
						|
import xmltodict
 | 
						|
import traceback
 | 
						|
 | 
						|
# androidKey
 | 
						|
# "androidKeyCount": "one" or "other" used to find matching key with quantity
 | 
						|
# "sentenceCase": true capitalize first word (must be called before addStart)
 | 
						|
# "ignoreCase": true ignore case difference between android EN and desktop EN values (some stuff are put in maj on android but not on desktop)
 | 
						|
# "addStart": "&" char to add as start char
 | 
						|
# "androidReplace": replace all occurences of key value pair
 | 
						|
 | 
						|
ALLOWED_ITEM_KEYS = ['message', 'description', 'comment', 'placeholders', 'androidKey', 'androidKeyCount', 'androidReplace', 'addStart', 'ignoreCase', 'sentenceCase']
 | 
						|
 | 
						|
SPECIFIC_LOCALES_MAPPING = {
 | 
						|
    'zh_CN': 'zh-rCN',
 | 
						|
    'pt_BR': 'pt-rBR',
 | 
						|
    'id': 'in'
 | 
						|
}
 | 
						|
 | 
						|
if len(sys.argv) != 3:
 | 
						|
    print(f"usage: {sys.argv[0]} <dst language i.e. 'de'> <android_root folder>")
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
dest = sys.argv[1]
 | 
						|
androidRoot = sys.argv[2]
 | 
						|
 | 
						|
desktopSrc = json.loads(open(f"_locales/en/messages.json",
 | 
						|
 "r").read())
 | 
						|
destFilePath = f"_locales/{dest}/messages.json"
 | 
						|
desktopDest = json.loads(open(destFilePath,
 | 
						|
 "r").read())
 | 
						|
 | 
						|
androidEnValueFile = f"{androidRoot}/res/values/strings.xml"
 | 
						|
 | 
						|
 | 
						|
def getAndroidTranslatedFile(androidRoot, dest):
 | 
						|
    if dest in SPECIFIC_LOCALES_MAPPING.keys():
 | 
						|
        return f"{androidRoot}/res/values-{SPECIFIC_LOCALES_MAPPING[dest]}/strings.xml"
 | 
						|
    return f"{androidRoot}/res/values-{dest}/strings.xml"
 | 
						|
 | 
						|
androidTranslatedValueFile = getAndroidTranslatedFile(androidRoot, dest)
 | 
						|
 | 
						|
def getDictFromFile(filepath, keyToSearch):
 | 
						|
    xml = open(filepath, "r").read()
 | 
						|
    asDict = xmltodict.parse(xml)['resources'][keyToSearch]
 | 
						|
    return [dict(item) for item in asDict]
 | 
						|
 | 
						|
def getStringFromFileAsJSON(filepath):
 | 
						|
    return getDictFromFile(filepath, 'string')
 | 
						|
 | 
						|
def getPluralsFromFileAsJSON(filepath):
 | 
						|
    plurals = getDictFromFile(filepath, 'plurals')
 | 
						|
    # we need to force plurals to be an array (if plurals contains only one item, the dict won't contain an array itself)
 | 
						|
    for item in plurals:
 | 
						|
        if not isinstance(item['item'], list):
 | 
						|
            item['item'] = [item['item']]
 | 
						|
 | 
						|
    return plurals
 | 
						|
 | 
						|
# read and extract values from xml file in EN android side
 | 
						|
androidEnJsonSingular = getStringFromFileAsJSON(androidEnValueFile)
 | 
						|
androidEnJsonPlurals = getPluralsFromFileAsJSON(androidEnValueFile)
 | 
						|
 | 
						|
# read and extract values from xml file in DESTINATION LANGUAGE android side
 | 
						|
androidDestJsonSingular = getStringFromFileAsJSON(androidTranslatedValueFile)
 | 
						|
androidDestJsonPlurals = getPluralsFromFileAsJSON(androidTranslatedValueFile)
 | 
						|
 | 
						|
# print(f"androidDestJsonSingular {androidDestJsonSingular}")
 | 
						|
# print(f"androidDestJsonPlurals {androidDestJsonPlurals}")
 | 
						|
# print(f"\n\n\n\n androidEnJsonSingular {androidEnJsonSingular}")
 | 
						|
# print(f"\n\n\n\n androidEnJsonPlurals {androidEnJsonPlurals}")
 | 
						|
 | 
						|
missingAndroidKeyCount = 0
 | 
						|
notMatchingCount = 0
 | 
						|
 | 
						|
def findCountInItem(quantityStr, items):
 | 
						|
    # print(f'searching qty: {quantityStr}, items: {items}')
 | 
						|
    found = [item for item in items if item['@quantity'] == quantityStr]
 | 
						|
    # print(f'findCountInItem: {found}, quantityStr: {quantityStr}')
 | 
						|
 | 
						|
    if len(found) != 1:
 | 
						|
        # special case for japanese. There is no plural, so all quantityString = `other`
 | 
						|
        if dest == 'ja':
 | 
						|
            found = [item for item in items if item['@quantity'] == 'other']
 | 
						|
            if len(found) != 1:
 | 
						|
                str = f'quantityStr not found: other'
 | 
						|
                raise KeyError(str)
 | 
						|
        else:
 | 
						|
            str = f'quantityStr not found: "{quantityStr}"'
 | 
						|
            raise KeyError(str)
 | 
						|
    return dict(found[0])
 | 
						|
 | 
						|
 | 
						|
def findByNameSingular(keySearchedFor, singularString):
 | 
						|
    found = [item for item in singularString if item['@name'] == keySearchedFor]
 | 
						|
    # print(f'findByNameSingular: searching {keySearchedFor}, found: {found}')
 | 
						|
 | 
						|
    if len(found) != 1:
 | 
						|
        str = f'android key singular not found: "{keySearchedFor}" but should have been found'
 | 
						|
        raise KeyError(str)
 | 
						|
    return found[0]
 | 
						|
 | 
						|
 | 
						|
def findByNamePlurals(keySearchedFor, pluralsString, quantityStr):
 | 
						|
    found = [item for item in pluralsString if item['@name'] == keySearchedFor]
 | 
						|
    if len(found) != 1:
 | 
						|
        str = f'android key plurals not found: "{keySearchedFor}" but should have been found'
 | 
						|
        raise KeyError(str)
 | 
						|
    # f = found[0]
 | 
						|
    # print(f'\t\tquantityStr {quantityStr}, found {found}, f {f}, pluralsString {pluralsString}')
 | 
						|
    return findCountInItem(quantityStr, found[0]['item'])
 | 
						|
 | 
						|
 | 
						|
def validateKeysPresent(items):
 | 
						|
    for keyItem, valueItem in items:
 | 
						|
        if keyItem not in ALLOWED_ITEM_KEYS:
 | 
						|
            print(f"Invalid key item: {keyItem}")
 | 
						|
            exit(1)
 | 
						|
        # print(f"keyItem: '{keyItem}', valueItem: '{valueItem}'")
 | 
						|
 | 
						|
 | 
						|
# morph a string from android syntax to desktop syntax. Like replacing char, or %s
 | 
						|
def morphToDesktopSyntax(androidString, desktopItem):
 | 
						|
    replaced = androidString.replace(r"\'", "'")
 | 
						|
 | 
						|
    if('sentenceCase' in desktopItem.keys() and desktopItem['sentenceCase']):
 | 
						|
        replaced = replaced.capitalize()
 | 
						|
 | 
						|
    if ('androidReplace' in desktopItem.keys()):
 | 
						|
        for key, value in desktopItem['androidReplace'].items():
 | 
						|
            replaced = replaced.replace(key.title(), value)
 | 
						|
            replaced = replaced.replace(key, value)
 | 
						|
 | 
						|
    # print(f"androidString: '{androidString}', replaced: '{replaced}'")
 | 
						|
    if ('addStart' in desktopItem.keys()):
 | 
						|
        toAdd = desktopItem['addStart']
 | 
						|
        replaced = f'{toAdd}{replaced}'
 | 
						|
    return replaced
 | 
						|
 | 
						|
    # morph a string from android syntax to desktop syntax. Like replacing char, or %s
 | 
						|
def morphToDesktopSyntaxTranslated(androidString, desktopItem):
 | 
						|
    replaced = androidString.replace(r"\'", "'")
 | 
						|
 | 
						|
    if('sentenceCase' in desktopItem.keys() and desktopItem['sentenceCase']):
 | 
						|
        replaced = replaced.capitalize()
 | 
						|
 | 
						|
    if ('androidReplace' in desktopItem.keys()):
 | 
						|
        for key, value in desktopItem['androidReplace'].items():
 | 
						|
            replaced = replaced.replace(key.title(), value)
 | 
						|
            replaced = replaced.replace(key, value)
 | 
						|
 | 
						|
    # print(f"desktopItem: '{desktopItem}', replaced: '{desktopItem}'")
 | 
						|
    if ('addStart' in desktopItem.keys()):
 | 
						|
        toAdd = desktopItem['addStart']
 | 
						|
        # special case for ja. appen the & and first char from desktop EN item
 | 
						|
        if dest == 'ja':
 | 
						|
            replaced = f'{replaced} ({toAdd}{desktopItem["message"][1]})'
 | 
						|
        else:
 | 
						|
            replaced = f'{toAdd}{replaced}'
 | 
						|
    return replaced
 | 
						|
 | 
						|
def getAndroidItem(androidKey, androidKeyCount, singularJson, pluralsJson):
 | 
						|
    # print(f"\tandroidKey: '{androidKey}'")
 | 
						|
    # print(f"\tandroidKeyCount: '{androidKeyCount}'")
 | 
						|
    if androidKeyCount:
 | 
						|
        return findByNamePlurals(androidKey, pluralsJson, androidKeyCount)
 | 
						|
    else:
 | 
						|
        return findByNameSingular(androidKey, singularJson)
 | 
						|
 | 
						|
def getAndroidKeyCountFromItem(item):
 | 
						|
    androidKeyCount = None
 | 
						|
    if 'androidKeyCount' in item.keys():
 | 
						|
        androidKeyCount = item['androidKeyCount']
 | 
						|
    return androidKeyCount
 | 
						|
 | 
						|
def keysDifference(src, dest):
 | 
						|
    srcKeys = set(src.keys())
 | 
						|
    destKeys = set(dest.keys())
 | 
						|
    return list (srcKeys - destKeys)
 | 
						|
 | 
						|
def addEnglishItemAsPlaceHolder(desktopDest, itemEnDesktop):
 | 
						|
    # add only if the key does not already exists on desktopDest
 | 
						|
    if key not in desktopDest.keys():
 | 
						|
        desktopDest[key] = itemEnDesktop
 | 
						|
 | 
						|
 | 
						|
# number of keys on src which do not exist at all on 'dest'
 | 
						|
# print('keysDifference:', len(keysDifference(desktopSrc, desktopDest)))
 | 
						|
 | 
						|
def doesAndroidEnAndDesktopMatches(txtEnDesktop, morphedEnAndroid, desktopItemEn):
 | 
						|
    if 'ignoreCase' in desktopItemEn.keys() and desktopItemEn['ignoreCase']:
 | 
						|
        return txtEnDesktop.lower() == morphedEnAndroid.lower()
 | 
						|
    return txtEnDesktop == morphedEnAndroid
 | 
						|
 | 
						|
###################  MAIN #####################
 | 
						|
for key, itemEnDesktop in desktopSrc.items():
 | 
						|
    # print(f"key: '{key}', itemEnDesktop: '{itemEnDesktop}'")
 | 
						|
    items = itemEnDesktop.items()
 | 
						|
    validateKeysPresent(items)
 | 
						|
    if 'androidKey' not in itemEnDesktop.keys():
 | 
						|
        # print('androidKey not found for {key}')
 | 
						|
        missingAndroidKeyCount = missingAndroidKeyCount + 1
 | 
						|
        # ENABLE ME to add a placeholder item from the EN file when it is missing on the target locale
 | 
						|
        # addEnglishItemAsPlaceHolder(desktopDest, itemEnDesktop)
 | 
						|
        continue
 | 
						|
    androidKey = itemEnDesktop['androidKey']
 | 
						|
    androidKeyCount = getAndroidKeyCountFromItem(itemEnDesktop)
 | 
						|
    # print(f'key: {key}, androidKey: {androidKey}, androidKeyCount: {androidKeyCount}')
 | 
						|
    txtEnDesktop = itemEnDesktop['message']
 | 
						|
    itemEnAndroid = getAndroidItem(androidKey, androidKeyCount, androidEnJsonSingular, androidEnJsonPlurals)
 | 
						|
 | 
						|
    txtEnAndroid = itemEnAndroid['#text']
 | 
						|
 | 
						|
    morphedEnAndroid = morphToDesktopSyntax(txtEnAndroid, itemEnDesktop)
 | 
						|
    if not doesAndroidEnAndDesktopMatches(txtEnDesktop, morphedEnAndroid, itemEnDesktop):
 | 
						|
        print(f'\t\tDOES NOT MATCH: "{txtEnDesktop}" vs "{morphedEnAndroid}", itemEnDesktop: {itemEnDesktop}\n\n')
 | 
						|
        notMatchingCount = notMatchingCount + 1
 | 
						|
    else:
 | 
						|
        # if it does match, find the corresponding value on the target language on android
 | 
						|
        # print(f'=============== EN to EN MATCH, continuing... : "{txtEnDesktop}" vs "{morphedEnAndroid}"')
 | 
						|
        try:
 | 
						|
            textTranslated = getAndroidItem(androidKey, androidKeyCount, androidDestJsonSingular, androidDestJsonPlurals)['#text']
 | 
						|
            # print(f'textTranslated: "{textTranslated}"')
 | 
						|
 | 
						|
            textMorphed = morphToDesktopSyntaxTranslated(textTranslated, itemEnDesktop)
 | 
						|
            existingItemTranslated = None
 | 
						|
            existingTranslation = None
 | 
						|
            if key in desktopDest.keys():
 | 
						|
                existingItemTranslated = desktopDest[key]
 | 
						|
                existingTranslation = existingItemTranslated['message']
 | 
						|
 | 
						|
            # print(f'existingItemTranslated: "{existingItemTranslated}"')
 | 
						|
            if existingTranslation != textMorphed:
 | 
						|
                print(f'not matching: "{existingTranslation}" and "{textMorphed}"')
 | 
						|
                if key not in desktopDest.keys():
 | 
						|
                    desktopDest[key] = {'message': textMorphed}
 | 
						|
                else:
 | 
						|
                    desktopDest[key]['message'] = textMorphed
 | 
						|
 | 
						|
        except KeyError:
 | 
						|
            print('KeyError exception:', traceback.format_exc())
 | 
						|
 | 
						|
 | 
						|
 | 
						|
# write the updated json dict to the file
 | 
						|
with open(destFilePath, 'w') as outfile:
 | 
						|
    json.dump(desktopDest, outfile, indent=4, ensure_ascii=False)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
print(f"total keys missing {missingAndroidKeyCount}") # androidKey set on desktop but not found on android EN resources
 | 
						|
print(f"total text not matching EN to EN {notMatchingCount}")
 |