#!/usr/bin/env python3 #coding=utf-8 from __future__ import print_function import json import os import io import sys import fontTables TRANSLATION_CPP = "Translation.cpp" try: to_unicode = unicode except NameError: to_unicode = str # Loading a single JSON file def loadJson(fileName, skipFirstLine): with io.open(fileName, mode="r", encoding="utf-8") as f: if skipFirstLine: f.readline() obj = json.loads(f.read()) return obj # Reading all language translations into a dictionary by langCode def readTranslations(jsonDir): langDict = {} # Read all translation files from the input dir for fileName in os.listdir(jsonDir): fileWithPath = os.path.join(jsonDir, fileName) lf = fileName.lower() # Read only translation_XX.json if lf.startswith("translation_") and lf.endswith(".json"): try: lang = loadJson(fileWithPath, False) except json.decoder.JSONDecodeError as e: print("Failed to decode " + lf) print(str(e)) sys.exit(2) # Extract lang code from file name langCode = fileName[12:-5].upper() # ...and the one specified in the JSON file... try: langCodeFromJson = lang['languageCode'] except KeyError: langCodeFromJson = "(missing)" # ...cause they should be the same! if langCode != langCodeFromJson: raise ValueError("Invalid languageCode " + langCodeFromJson + " in file " + fileName) langDict[langCode] = lang return langDict def writeStart(f): f.write( to_unicode( """// WARNING: THIS FILE WAS AUTO GENERATED BY make_translation.py. PLEASE DO NOT EDIT. #include "Translation.h" #ifndef LANG #define LANG_EN #endif """)) def escapeC(s): return s.replace("\"", "\\\"") def getConstants(): # Extra constants that are used in the firmware that are shared across all languages consants =[] consants.append(('SymbolPlus','+')) consants.append(('SymbolMinus','-')) consants.append(('SymbolSpace',' ')) consants.append(('SymbolDot','.')) consants.append(('SymbolDegC','C')) consants.append(('SymbolDegF','F')) consants.append(('SymbolMinutes','M')) consants.append(('SymbolSeconds','S')) consants.append(('SymbolWatts','W')) consants.append(('SymbolVolts','V')) consants.append(('SymbolDC','DC')) consants.append(('SymbolCellCount','S')) consants.append(('SymbolVersionNumber','V2.06')) return consants def getTipModelEnumTS80(): constants = [] constants.append("B02") constants.append("D25") constants.append("TS80") # end of miniware constants.append("User") # User return constants def getTipModelEnumTS100(): constants = [] constants.append("B02") constants.append("D24") constants.append("BC2") constants.append(" C1") constants.append("TS100")# end of miniware constants.append("BC2") constants.append("Hakko")# end of hakko constants.append("User") return constants def getDebugMenuHeaders(): constants = [] constants.append("DateHere") constants.append("Heap: ") constants.append("HWMG: ") constants.append("HWMP: ") constants.append("HWMM: ") constants.append("Time: ") constants.append("Move: ") constants.append("RTip: ") constants.append("CTip: ") constants.append("Vin: ") constants.append("THan: ") constants.append("Model: ") constants.append("Tres: ") return constants def getLetterCounts(defs, lang): textList = [] #iterate over all strings obj = lang['menuOptions'] for mod in defs['menuOptions']: eid = mod['id'] textList.append(obj[eid]['desc']) obj = lang['messages'] for mod in defs['messages']: eid = mod['id'] if eid not in obj: textList.append(mod['default']) else: textList.append(obj[eid]) obj = lang['characters'] for mod in defs['characters']: eid = mod['id'] textList.append(obj[eid]) obj = lang['menuOptions'] for mod in defs['menuOptions']: eid = mod['id'] if lang['menuDouble']: textList.append(obj[eid]['text2'][0]) textList.append(obj[eid]['text2'][1]) else: textList.append(obj[eid]['text']) obj = lang['menuGroups'] for mod in defs['menuGroups']: eid = mod['id'] textList.append(obj[eid]['text2'][0]) textList.append(obj[eid]['text2'][1]) obj = lang['menuGroups'] for mod in defs['menuGroups']: eid = mod['id'] textList.append(obj[eid]['desc']) constants = getConstants() for x in constants: textList.append(x[1]) textList.extend(getDebugMenuHeaders()) textList.extend(getTipModelEnumTS100()) textList.extend(getTipModelEnumTS80()) # collapse all strings down into the composite letters and store totals for these symbolCounts = {} for line in textList: line = line.replace('\n', '').replace('\r', '') line = line.replace('\\n', '').replace('\\r', '') if len(line): #print(line) for letter in line: symbolCounts[letter] = symbolCounts.get(letter, 0) + 1 symbolCounts = sorted( symbolCounts.items(), key=lambda kv: kv[1]) # swap to Big -> little sort order symbolCounts = list(map(lambda x: x[0], symbolCounts)) symbolCounts.reverse() return symbolCounts def getFontMapAndTable(textList): # the text list is sorted # allocate out these in their order as number codes symbolMap = {} symbolMap['\n'] = '\\x01' index = 2 # start at 2, as 0= null terminator,1 = new line forcedFirstSymbols = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] #enforce numbers are first for sym in forcedFirstSymbols: symbolMap[sym] = "\\x%0.2X" % index index = index + 1 if len(textList) > (253 - len(forcedFirstSymbols)): print('Error, too many used symbols for this version') exit(1) print('Generating fonts for {} symbols'.format(len(textList))) for sym in textList: if sym not in symbolMap: symbolMap[sym] = "\\x%0.2X" % index index = index + 1 # Get the font table fontTableStrings = [] fontSmallTableStrings = [] fontTable = fontTables.getFontMap() fontSmallTable = fontTables.getSmallFontMap() for sym in forcedFirstSymbols: if sym not in fontTable: print('Missing Large font element for {}'.format(sym)) exit(1) fontLine = fontTable[sym] fontTableStrings.append(fontLine + "//{} -> {}".format(symbolMap[sym],sym)) if sym not in fontSmallTable: print('Missing Small font element for {}'.format(sym)) exit(1) fontLine = fontSmallTable[sym] fontSmallTableStrings.append(fontLine + "//{} -> {}".format(symbolMap[sym],sym)) for sym in textList: if sym not in fontTable: print('Missing Large font element for {}'.format(sym)) exit(1) if sym not in forcedFirstSymbols: fontLine = fontTable[sym] fontTableStrings.append(fontLine + "//{} -> {}".format(symbolMap[sym],sym)) if sym not in fontSmallTable: print('Missing Small font element for {}'.format(sym)) exit(1) fontLine = fontSmallTable[sym] fontSmallTableStrings.append(fontLine + "//{} -> {}".format(symbolMap[sym],sym)) outputTable = "const uint8_t USER_FONT_12[] = {" + to_unicode("\n") for line in fontTableStrings: # join font table int one large string outputTable = outputTable + line + to_unicode("\n") outputTable = outputTable + "};" + to_unicode("\n") outputTable = outputTable + "const uint8_t USER_FONT_6x8[] = {" + to_unicode( "\n") for line in fontSmallTableStrings: # join font table int one large string outputTable = outputTable + line + to_unicode("\n") outputTable = outputTable + "};" + to_unicode("\n") return (outputTable, symbolMap) def convStr(symbolConversionTable, text): # convert all of the symbols from the string into escapes for their content outputString = "" for c in text.replace('\\r', '').replace('\\n','\n'): if c not in symbolConversionTable: print('Missing font definition for {}'.format(c)) else: outputString = outputString + symbolConversionTable[c] return outputString def writeLanguage(languageCode, defs, f): print("Generating block for " + languageCode) lang = langDict[languageCode] #Iterate over all of the text to build up the symbols & counts textList = getLetterCounts(defs, lang) # From the letter counts, need to make a symbol translator & write out the font (fontTableText, symbolConversionTable) = getFontMapAndTable(textList) f.write(to_unicode("\n#ifdef LANG_" + languageCode + "\n")) f.write(fontTableText) try: langName = lang['languageLocalName'] except KeyError: langName = languageCode f.write(to_unicode("// ---- " + langName + " ----\n\n")) # ----- Writing SettingsDescriptions obj = lang['menuOptions'] f.write(to_unicode("const char* SettingsDescriptions[] = {\n")) maxLen = 25 for mod in defs['menuOptions']: eid = mod['id'] if 'feature' in mod: f.write(to_unicode("#ifdef " + mod['feature'] + "\n")) f.write(to_unicode(" /* " + eid.ljust(maxLen)[:maxLen] + " */ ")) f.write( to_unicode("\"" + convStr(symbolConversionTable, (obj[eid]['desc'])) + "\"," + "//{} \n".format(obj[eid]['desc']))) if 'feature' in mod: f.write(to_unicode("#endif\n")) f.write(to_unicode("};\n\n")) # ----- Writing Message strings obj = lang['messages'] for mod in defs['messages']: eid = mod['id'] if eid not in obj: f.write( to_unicode("const char* " + eid + " = \"" + convStr(symbolConversionTable, (mod['default'])) + "\";"+ "//{} \n".format(mod['default']))) else: f.write( to_unicode("const char* " + eid + " = \"" + convStr(symbolConversionTable, (obj[eid])) + "\";"+ "//{} \n".format(obj[eid]))) f.write(to_unicode("\n")) # ----- Writing Characters obj = lang['characters'] for mod in defs['characters']: eid = mod['id'] f.write( to_unicode("const char* " + eid + " = \"" + convStr(symbolConversionTable, obj[eid]) + "\";"+ "//{} \n".format(obj[eid]))) f.write(to_unicode("\n")) # Write out firmware constant options constants = getConstants() for x in constants: f.write( to_unicode("const char* " + x[0] + " = \"" + convStr(symbolConversionTable, x[1]) + "\";"+ "//{} \n".format(x[1]))) f.write(to_unicode("\n")) # Write out tip model strings f.write(to_unicode("const char* TipModelStrings[] = {\n")) f.write(to_unicode("#ifdef MODEL_TS100\n")) for c in getTipModelEnumTS100(): f.write(to_unicode("\t \"" + convStr(symbolConversionTable, c) + "\","+ "//{} \n".format(c))) f.write(to_unicode("#else\n")) for c in getTipModelEnumTS80(): f.write(to_unicode("\t \"" + convStr(symbolConversionTable, c) + "\","+ "//{} \n".format(c))) f.write(to_unicode("#endif\n")) f.write(to_unicode("};\n\n")) # -- Debugging Menu f.write(to_unicode("const char* DebugMenu[] = {\n")) for c in getDebugMenuHeaders(): f.write(to_unicode("\t \"" + convStr(symbolConversionTable, c) + "\","+ "//{} \n".format(c))) f.write(to_unicode("};\n\n")) # ----- Menu Options # Menu type f.write( to_unicode( "const enum ShortNameType SettingsShortNameType = SHORT_NAME_" + ("DOUBLE" if lang['menuDouble'] else "SINGLE") + "_LINE;\n")) # ----- Writing SettingsDescriptions obj = lang['menuOptions'] f.write(to_unicode("const char* SettingsShortNames[][2] = {\n")) maxLen = 25 for mod in defs['menuOptions']: eid = mod['id'] if 'feature' in mod: f.write(to_unicode("#ifdef " + mod['feature'] + "\n")) f.write(to_unicode(" /* " + eid.ljust(maxLen)[:maxLen] + " */ ")) if lang['menuDouble']: f.write( to_unicode( "{ \"" + convStr(symbolConversionTable, (obj[eid]['text2'][0])) + "\", \"" + convStr(symbolConversionTable, (obj[eid]['text2'][1])) + "\" },"+ "//{} \n".format(obj[eid]['text2']))) else: f.write( to_unicode("{ \"" + convStr(symbolConversionTable, (obj[eid]['text'])) + "\" },"+ "//{} \n".format(obj[eid]['text']))) if 'feature' in mod: f.write(to_unicode("#endif\n")) f.write(to_unicode("};\n\n")) # ----- Writing Menu Groups obj = lang['menuGroups'] f.write( to_unicode("const char* SettingsMenuEntries[" + str(len(obj)) + "] = {\n")) maxLen = 25 for mod in defs['menuGroups']: eid = mod['id'] f.write(to_unicode(" /* " + eid.ljust(maxLen)[:maxLen] + " */ ")) f.write( to_unicode("\"" + convStr(symbolConversionTable, (obj[eid]['text2'][0]) + "\\n" + obj[eid]['text2'][1]) + "\","+ "//{} \n".format(obj[eid]['text2']))) f.write(to_unicode("};\n\n")) # ----- Writing Menu Groups Descriptions obj = lang['menuGroups'] f.write( to_unicode("const char* SettingsMenuEntriesDescriptions[" + str(len(obj)) + "] = {\n")) maxLen = 25 for mod in defs['menuGroups']: eid = mod['id'] f.write(to_unicode(" /* " + eid.ljust(maxLen)[:maxLen] + " */ ")) f.write( to_unicode("\"" + convStr(symbolConversionTable, (obj[eid]['desc'])) + "\","+ "//{} \n".format(obj[eid]['desc']))) f.write(to_unicode("};\n\n")) # ----- Block end f.write(to_unicode("#endif\n")) def read_opts(): """ Reading input parameters First parameter = json directory Second parameter = target directory """ if len(sys.argv) > 1: jsonDir = sys.argv[1] else: jsonDir = "." if len(sys.argv) > 2: outFile = sys.argv[2] else: outDir = os.path.relpath(jsonDir + "/../workspace/TS100/src/") outFile = os.path.join(outDir, TRANSLATION_CPP) if len(sys.argv) > 3: raise Exception("Too many parameters!") return jsonDir, outFile def orderOutput(langDict): # These languages go first mandatoryOrder = ['EN'] # Then add all others in alphabetical order sortedKeys = sorted(langDict.keys()) # Add the rest as they come for key in sortedKeys: if key not in mandatoryOrder: mandatoryOrder.append(key) return mandatoryOrder def writeTarget(outFile, defs, langCodes): # Start writing the file with io.open(outFile, 'w', encoding='utf-8', newline="\n") as f: writeStart(f) for langCode in langCodes: writeLanguage(langCode, defs, f) if __name__ == "__main__": try: jsonDir, outFile = read_opts() except: print("usage: make_translation.py {json dir} {cpp dir}") sys.exit(1) print("Making " + outFile + " from " + jsonDir) langDict = readTranslations(jsonDir) defs = loadJson(os.path.join(jsonDir, "translations_def.js"), True) langCodes = orderOutput(langDict) writeTarget(outFile, defs, langCodes) print("Done")