Commit 1954cd20 authored by DoinLakeFlyer's avatar DoinLakeFlyer

parent 9e85745e
# QGroundControl string translations
QGC uses the standard Qt Linguist mechanisms for string translation. QGC uses crowd sourced string translation through a [Crowdin project]( for translation).
## C++ and Qml code strings
These are coded using the standard Qt tr() for C++ and qsTr() for Qml mechanisms.
## Translating strings within Json files
QGC uses json files internally for metadata. These files need to be translated as well. There is a [python parser]( which is used to find all the json files in the source tree and pull all the strings out for translation. This parser outputs the localization file for json strings in Qt .ts file format.
In order for the parser to know which strings must be translated additional keys must be available at the root object level.
> Important: Json files which have the same name are not allowed. Since the name is used as the context for the translation lookup it must be unique. The parse will throw an error if it finds duplicate file names.
> Important: The root file name for the json file must be the same as the root filename for the Qt resource alias. This due to the fact that the root filename is used as the translation context. The json parser reads files from the file system and sees file system names. Whereas the QGC C++ code reads json files from the QT resource system and see the file alias as the full path and root name.
### Specifying known file type
The parser supports two known file types: "MAVCmdInfo" and "FactMetaData". If your json file is one of these types you should place a `fileType` key at the root with one of these values. This will cause the parser to use these defaults for instructions:
#### MAVCmdInfo
"translateKeys": "label,enumStrings,friendlyName,description",
"arrayIDKeys": "rawName,comment"
#### FactMetaData
"translateKeys": "shortDescription,longDescription,enumStrings"
"arrayIDKeys": "name"
### Manually specify parser instructions
For this case dont include the `fileType` key/value pair. And include the followings keys (as needed) in the root object:
* `translateKeys` This key is a string which is a list of all the keys which should be translated.
* `arrayIDKeys` The json localization parser provides additional information to the translator about where this string came from in the json hierarchy. If there is an array in the json, just displaying an array index as to where this came from is not that helpful. In most cases there is a key within each array element for which the value is unique. If this is the case then specify this key name(s) as the value for `arrayIDKeys`.
### Disambiguation
This is used when you have two strings in the same file which are equal, but there meaning ar different enough that when translated they may each have their own different translation. In order to specific that you include a special prefix marker in the string which includes comments to the translator to explain the specifics of the string.
"foo": "#loc.disambiguation#This is the foo version of baz#baz"
"bar": "#loc.disambiguation#This is the bar version of baz#baz"
In the example above "baz" is the string which is the same for two different keys. The prefix `#loc.disambiguation#` indicates a disambiguation is to follow which is the string between the next set of `#`s.
## Crowdin integration
Crowdin is configured to automatically sychronize the qgc.ts file once a day. So it will pick up any new changes automatically. Once it has processed those changes it will submit a pull request back with the translations.
#!/usr/bin/env python
import os
import json
from xml.dom.minidom import parse
import xml.dom.minidom
import codecs
import sys
qgcFileTypeKey = "fileType"
translateKeysKey = "translateKeys"
arrayIDKeysKey = "arrayIDKeys"
disambiguationPrefix = "#loc.disambiguation#"
def parseJsonObjectForTranslateKeys(jsonObjectHierarchy, jsonObject, translateKeys, arrayIDKeys, locStringDict):
for translateKey in translateKeys:
if (translateKey in jsonObject):
locStr = jsonObject[translateKey]
currentHierarchy = jsonObjectHierarchy + "." + translateKey
if locStr in locStringDict:
# Duplicate of an existing string
# First time we are seeing this string
locStringDict[locStr] = [ currentHierarchy ]
for key in jsonObject:
currentHierarchy = jsonObjectHierarchy + "." + key
if (type(jsonObject[key]) == type({})):
parseJsonObjectForTranslateKeys(currentHierarchy, jsonObject[key], translateKeys,arrayIDKeys, locStringDict)
elif (type(jsonObject[key]) == type([])):
parseJsonArrayForTranslateKeys(currentHierarchy, jsonObject[key], translateKeys, arrayIDKeys, locStringDict)
def parseJsonArrayForTranslateKeys(jsonObjectHierarchy, jsonArray, translateKeys, arrayIDKeys, locStringDict):
for index in range(0, len(jsonArray)):
jsonObject = jsonArray[index]
arrayIndexStr = str(index)
for arrayIDKey in arrayIDKeys:
if arrayIDKey in jsonObject.keys():
arrayIndexStr = jsonObject[arrayIDKey]
currentHierarchy = jsonObjectHierarchy + "[" + arrayIndexStr + "]"
parseJsonObjectForTranslateKeys(currentHierarchy, jsonObject, translateKeys, arrayIDKeys, locStringDict)
def addLocKeysBasedOnQGCFileType(jsonPath, jsonDict):
# Instead of having to add the same keys over and over again in a pile of files we add them here automatically based on file type
if qgcFileTypeKey in jsonDict:
qgcFileType = jsonDict[qgcFileTypeKey]
translateKeyValue = ""
arrayIDKeysKeyValue = ""
if qgcFileType == "MavCmdInfo":
translateKeyValue = "label,enumStrings,friendlyName,description,category"
arrayIDKeysKeyValue = "rawName,comment"
elif qgcFileType == "FactMetaData":
translateKeyValue = "shortDescription,longDescription,enumStrings"
arrayIDKeysKeyValue = "name"
if translateKeysKey not in jsonDict and translateKeyValue != "":
jsonDict[translateKeysKey] = translateKeyValue
if arrayIDKeysKey not in jsonDict and arrayIDKeysKeyValue != "":
jsonDict[arrayIDKeysKey] = arrayIDKeysKeyValue
def parseJson(jsonPath, locStringDict):
jsonFile = open(jsonPath)
jsonDict = json.load(jsonFile)
if (type(jsonDict) != type({})):
addLocKeysBasedOnQGCFileType(jsonPath, jsonDict)
if (not translateKeysKey in jsonDict):
translateKeys = jsonDict[translateKeysKey].split(",")
arrayIDKeys = jsonDict.get(arrayIDKeysKey, "").split(",")
parseJsonObjectForTranslateKeys("", jsonDict, translateKeys, arrayIDKeys, locStringDict)
def walkDirectoryTreeForJsonFiles(dir, multiFileLocArray):
for filename in os.listdir(dir):
path = os.path.join(dir, filename)
if (os.path.isfile(path) and filename.endswith(".json")):
#print "json",path
singleFileLocStringDict = {}
parseJson(path, singleFileLocStringDict)
if len(singleFileLocStringDict.keys()):
# Check for duplicate file names
for entry in multiFileLocArray:
if entry[0] == filename:
print "Error: Duplicate filenames: %s paths: %s %s" % (filename, path, entry[1])
multiFileLocArray.append([filename, path, singleFileLocStringDict])
if (os.path.isdir(path)):
walkDirectoryTreeForJsonFiles(path, multiFileLocArray)
def appendToQGCTSFile(multiFileLocArray):
originalTSFile ='qgc.ts', 'r', "utf-8")
newTSFile ='', 'w', "utf-8")
line = originalTSFile.readline()
while (line != "</TS>\n"):
line = originalTSFile.readline()
for entry in multiFileLocArray:
newTSFile.write(" <name>%s</name>\n" % entry[0])
singleFileLocStringDict = entry[2]
for locStr in singleFileLocStringDict.keys():
disambiguation = ""
if locStr.startswith(disambiguationPrefix):
workStr = locStr[len(disambiguationPrefix):]
terminatorIndex = workStr.find("#")
if terminatorIndex == -1:
print "Bad disambiguation %1 '%2'" % (entry[0], locStr)
disambiguation = workStr[:terminatorIndex]
locStr = workStr[terminatorIndex+1:]
newTSFile.write(" <message>\n")
if len(disambiguation):
newTSFile.write(" <comment>%s</comment>\n" % disambiguation)
extraCommentStr = ""
for jsonHierachy in singleFileLocStringDict[locStr]:
extraCommentStr += "%s, " % jsonHierachy
newTSFile.write(" <extracomment>%s</extracomment>\n" % extraCommentStr)
newTSFile.write(" <location filename=\"%s\"/>\n" % entry[1])
newTSFile.write(unicode(" <source>%s</source>\n") % locStr)
newTSFile.write(" <translation type=\"unfinished\"></translation>\n")
newTSFile.write(" </message>\n")
def main():
multiFileLocArray = []
walkDirectoryTreeForJsonFiles("../src", multiFileLocArray)
if __name__ == '__main__':
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment