mirror of
https://github.com/GAM-team/GAM.git
synced 2025-07-09 22:23:35 +00:00
Multiple fixes (#713)
* - Update new create drive options * - Add gender, keyword, languages attributes to user
This commit is contained in:
@ -436,6 +436,7 @@ Named items
|
||||
email|emails|otheremail|otheremails|
|
||||
externalids|externalid|
|
||||
familyname|firstname|fullname|givenname|lastname|name|
|
||||
gender|
|
||||
id|
|
||||
ims|im|
|
||||
includeinglobaladdresslist|gal|
|
||||
@ -444,6 +445,8 @@ Named items
|
||||
ismailboxsetup|
|
||||
isenforcedin2sv|is2svenforced|
|
||||
isenrolledin2sv|is2svenrolled|
|
||||
keyword|keywords|
|
||||
language|languages|
|
||||
lastlogintime|
|
||||
locations|location|
|
||||
noneditablealiases|aliases|nicknames|
|
||||
@ -463,11 +466,18 @@ Named items
|
||||
familyname|lastname|givenname|firstname|email
|
||||
|
||||
Named Lists
|
||||
Lists can be in the following formats
|
||||
Items, separated by commas, without spaces or commas in the items themselves: item(,item)*
|
||||
Items, separated by spaces, without spaces or commas in the items themselves: "item( item)*"
|
||||
Items, separated by commas, with spaces or commas in the items themselves: "'it em'(,'it,em')*"
|
||||
Items, separated by spaces, with spaces or commas in the items themselves: "'it em'( 'it,em')*"
|
||||
|
||||
Items in a list can be separated by commas or spaces; if an item itself contains a comma, a space or a single quote, special quoting must be used.
|
||||
Typically, you will enclose the entire list in double quotes and quote each item in the list as detailed below.
|
||||
|
||||
Items, separated by commas, without spaces, commas or single quotes in the items themselves
|
||||
"item,item,item"
|
||||
Items, separated by spaces, without spaces, commas or single quotes in the items themselves
|
||||
"item item item"
|
||||
Items, separated by commas, with spaces, commas or single quotes in the items themselves
|
||||
"'it em','it,em',\"it'em\""
|
||||
Items, separated by spaces, with spaces, commas or single quotes in the items themselves
|
||||
"'it em' 'it,em' \"it'em\""
|
||||
|
||||
<ACLList> ::= "<ACLScope>(,<ACLScope>)*"
|
||||
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
|
||||
@ -527,9 +537,9 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
|
||||
Item attributes
|
||||
<BuildingAttributes> ::=
|
||||
(buildingid <String>)|
|
||||
(description <String>)|
|
||||
(floors <FloorNameList>)|
|
||||
(id <String>)|
|
||||
(latitude <Float>)|
|
||||
(longitude <Float>)|
|
||||
(name <String>)
|
||||
@ -621,7 +631,7 @@ Item attributes
|
||||
<ResourceAttributes> ::=
|
||||
(buildingid <BuildingID>)|
|
||||
(capacity <Number>)|
|
||||
(category other|room|conference_room)|
|
||||
(category other|room|conference_room|unknown_category)|
|
||||
(description <String>)|
|
||||
(features <FeatureNameList>)|
|
||||
(floor <FloorName>)|
|
||||
@ -646,8 +656,11 @@ Item attributes
|
||||
(externalid clear|(account|customer|login_id|network|organization|<String> <String>))|
|
||||
(firstname|givenname <String>)|
|
||||
(gal|includeinglobaladdresslist <Boolean>)|
|
||||
(gender clear|(female|male|unknown|(other <String>) [addressmeas <String>]))|
|
||||
(im clear|(type work|home|other|(custom <String>) protocol aim|gtalk|icq|jabber|msn|net_meeting|qq|skype|yahoo|(custom_protocol <String>) <String> [notprimary|primary]))|
|
||||
(ipwhitelisted <Boolean>)|
|
||||
(keyword clear|(occupation|outlook|(custom <string>) <String>))|
|
||||
(language clear|<LanguageList>)|
|
||||
(lastname|familyname <String>)|
|
||||
(location clear|(type default|desk|<String> area <String> [building|buildingid <BuildingID>] [floor|floorname <FloorName>] [section|floorsection <String>] [desk|deskcode <String>] endlocation))|
|
||||
(note clear|([text_plain|text_html] <String>|(file <FileName> [charset <Charset>])))|
|
||||
@ -821,13 +834,6 @@ Prints no header row and deviceId for specified CrOS devices.
|
||||
gam print cros ... basic|full
|
||||
Prints a header row and selected fields for specified CrOS devices.
|
||||
|
||||
Print a header row and selected fields for specified CrOS devices; deviceId is the always the first column.
|
||||
If you specify allfields|basic|full, all other column headers are sorted.
|
||||
If you specify <CrOSFieldName>* | fields <CrOSFieldNameList>, the column headers appear in the order specified.
|
||||
gam [<CrOSTypeEntity>] print cros [todrive [<ToDriveAttributes>]] [query <QueryCrOS>]|[select <CrOSTypeEntity>] [limittoou <OrgUnitItem>]
|
||||
[orderby <CrOSOrderByFieldName> [ascending|descending]] [nolists|recentusers|timeranges|devicefiles] [listlimit <Number>] [start <Date>] [end <Date>]
|
||||
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>]
|
||||
|
||||
The basic argument outputs these column headers: deviceId,annotatedAssetId,annotatedLocation,annotatedUser,lastSync,notes,serialNumber,status
|
||||
The allfields/full arguments output all column headers including three headers, recentUsers, activeTimeRanges and deviceFiles,
|
||||
that repeat with two/four/two subvalues each, yielding a large number of columns that make the output hard to process.
|
||||
@ -1039,7 +1045,7 @@ gam <UserTypeEntity> show fileinfo <DriveFileID> [allfields|<DriveFieldName>*]
|
||||
gam <UserTypeEntity> show filerevisions <DriveFileID>
|
||||
gam <UserTypeEntity> show filetree [anyowner] (orderby <DriveOrderByFieldName> [ascending|descending])*
|
||||
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttributes>*
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttributes>* [csv] [todrive]
|
||||
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttributes>*
|
||||
gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>) [format <FileFormatList>] [targetfolder <FilePath>] [revision <Number>]
|
||||
gam <UserTypeEntity> delete|del drivefile <DriveFileID>|<DriveFileURL>|(query:<QueryDriveFile>) [purge|untrash]
|
||||
|
95
src/gam.py
95
src/gam.py
@ -4346,7 +4346,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
|
||||
parameters[DFA_OCR] = True
|
||||
i += 1
|
||||
elif myarg == u'ocrlanguage':
|
||||
parameters[DFA_OCRLANGUAGE] = sys.argv[i+1]
|
||||
parameters[DFA_OCRLANGUAGE] = LANGUAGE_CODES_MAP.get(sys.argv[i+1].lower(), sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in DRIVEFILE_LABEL_CHOICES_MAP:
|
||||
body.setdefault(u'labels', {})
|
||||
@ -4461,9 +4461,9 @@ def doUpdateDriveFile(users):
|
||||
print u'Successfully copied %s to %s' % (fileId, result[u'id'])
|
||||
|
||||
def createDriveFile(users):
|
||||
csv_output = None
|
||||
csv_output = to_drive = False
|
||||
csv_rows = []
|
||||
csv_titles = [u'title',u'id']
|
||||
csv_titles = [u'User', u'title', u'id']
|
||||
media_body = None
|
||||
body, parameters = initializeDriveFileAttributes()
|
||||
i = 5
|
||||
@ -4475,6 +4475,9 @@ def createDriveFile(users):
|
||||
elif myarg == u'csv':
|
||||
csv_output = True
|
||||
i += 1
|
||||
elif myarg == u'todrive':
|
||||
to_drive = True
|
||||
i += 1
|
||||
else:
|
||||
i = getDriveFileAttribute(i, body, parameters, myarg, False)
|
||||
for user in users:
|
||||
@ -4495,17 +4498,14 @@ def createDriveFile(users):
|
||||
supportsTeamDrives=True)
|
||||
titleInfo = u'{0}({1})'.format(result[u'title'], result[u'id'])
|
||||
if csv_output:
|
||||
csv_rows.append({
|
||||
u'title': result[u'title'],
|
||||
u'id': result[u'id']
|
||||
})
|
||||
csv_rows.append({u'User': user, u'title': result[u'title'], u'id': result[u'id']})
|
||||
else:
|
||||
if parameters[DFA_LOCALFILENAME]:
|
||||
print u'Successfully uploaded %s to Drive File %s' % (parameters[DFA_LOCALFILENAME], titleInfo)
|
||||
else:
|
||||
print u'Successfully created Drive %s %s' % ([u'Folder', u'File'][result[u'mimeType'] != MIMETYPE_GA_FOLDER], titleInfo)
|
||||
if csv_output:
|
||||
writeCSVfile(csv_rows, csv_titles, u'Files', False)
|
||||
writeCSVfile(csv_rows, csv_titles, u'Files', to_drive)
|
||||
|
||||
def downloadDriveFile(users):
|
||||
i = 5
|
||||
@ -6423,7 +6423,7 @@ def doGetUserSchema():
|
||||
_showSchema(schema)
|
||||
|
||||
def getUserAttributes(i, cd, updateCmd=False):
|
||||
def getEntryType(i, entry, entryTypes, setTypeCustom=True):
|
||||
def getEntryType(i, entry, entryTypes, setTypeCustom=True, customKeyword=u'custom', customTypeKeyword=u'customType'):
|
||||
""" Get attribute entry type
|
||||
entryTypes is list of pre-defined types, a|b|c
|
||||
Allow a|b|c|<String>, a|b|c|custom <String>
|
||||
@ -6435,26 +6435,25 @@ def getUserAttributes(i, cd, updateCmd=False):
|
||||
"""
|
||||
utype = sys.argv[i]
|
||||
ltype = utype.lower()
|
||||
if ltype == u'custom':
|
||||
if ltype == customKeyword:
|
||||
i += 1
|
||||
utype = sys.argv[i]
|
||||
ltype = utype.lower()
|
||||
if ltype in entryTypes:
|
||||
entry[u'type'] = ltype
|
||||
entry.pop(u'customType', None)
|
||||
entry.pop(customTypeKeyword, None)
|
||||
else:
|
||||
entry[u'customType'] = utype
|
||||
entry[customTypeKeyword] = utype
|
||||
if setTypeCustom:
|
||||
entry[u'type'] = u'custom'
|
||||
entry[u'type'] = customKeyword
|
||||
else:
|
||||
entry.pop(u'type', None)
|
||||
return i+1
|
||||
|
||||
def checkClearBodyList(i, body, itemName):
|
||||
if sys.argv[i].lower() == u'clear':
|
||||
if itemName in body:
|
||||
del body[itemName]
|
||||
body.setdefault(itemName, None)
|
||||
body.pop(itemName, None)
|
||||
body[itemName] = None
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -6553,6 +6552,28 @@ def getUserAttributes(i, cd, updateCmd=False):
|
||||
if body[u'orgUnitPath'][0] != u'/':
|
||||
body[u'orgUnitPath'] = u'/%s' % body[u'orgUnitPath']
|
||||
i += 2
|
||||
elif myarg in [u'language', u'languages']:
|
||||
i += 1
|
||||
if checkClearBodyList(i, body, u'languages'):
|
||||
i += 1
|
||||
continue
|
||||
for language in sys.argv[i].replace(u',', u' ').split():
|
||||
if language.lower() in LANGUAGE_CODES_MAP:
|
||||
appendItemToBodyList(body, u'languages', {u'languageCode': LANGUAGE_CODES_MAP[language.lower()]})
|
||||
else:
|
||||
appendItemToBodyList(body, u'languages', {u'customLanguage': language})
|
||||
i += 1
|
||||
elif myarg == u'gender':
|
||||
i += 1
|
||||
if checkClearBodyList(i, body, u'gender'):
|
||||
i += 1
|
||||
continue
|
||||
gender = {}
|
||||
i = getEntryType(i, gender, USER_GENDER_TYPES, customKeyword=u'other', customTypeKeyword=u'customGender')
|
||||
if (i < len(sys.argv)) and (sys.argv[i].lower() == u'addressmeas'):
|
||||
gender[u'addressMeAs'] = getString(i+1, u'String')
|
||||
i += 2
|
||||
body[u'gender'] = gender
|
||||
elif myarg in [u'address', u'addresses']:
|
||||
i += 1
|
||||
if checkClearBodyList(i, body, u'addresses'):
|
||||
@ -6846,6 +6867,16 @@ def getUserAttributes(i, cd, updateCmd=False):
|
||||
else:
|
||||
systemErrorExit(3, '%s is not a valid argument for user posix details. Make sure user posix details end with an endposix argument')
|
||||
appendItemToBodyList(body, u'posixAccounts', posix, checkSystemId=True)
|
||||
elif myarg in [u'keyword', u'keywords']:
|
||||
i += 1
|
||||
if checkClearBodyList(i, body, u'keywords'):
|
||||
i += 1
|
||||
continue
|
||||
keyword = {}
|
||||
i = getEntryType(i, keyword, USER_KEYWORD_TYPES, customKeyword=u'custom', customTypeKeyword=u'customType')
|
||||
keyword[u'value'] = sys.argv[i]
|
||||
i += 1
|
||||
appendItemToBodyList(body, u'keywords', keyword)
|
||||
elif myarg == u'clearschema':
|
||||
if not updateCmd:
|
||||
systemErrorExit(2, '%s is not a valid create user argument.' % sys.argv[i])
|
||||
@ -7650,6 +7681,8 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
||||
elif params[u'type'] == u'string':
|
||||
if attrib == u'description':
|
||||
value = value.replace(u'\\n', u'\n')
|
||||
elif attrib == u'primaryLanguage':
|
||||
value = LANGUAGE_CODES_MAP.get(value.lower(), value)
|
||||
elif params[u'description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased.
|
||||
value = value.upper()
|
||||
elif value.lower() in true_values:
|
||||
@ -8683,6 +8716,15 @@ def doGetUserInfo(user_email=None):
|
||||
print utils.convertUTF8(u'First Name: %s' % user[u'name'][u'givenName'])
|
||||
if u'name' in user and u'familyName' in user[u'name']:
|
||||
print utils.convertUTF8(u'Last Name: %s' % user[u'name'][u'familyName'])
|
||||
if u'languages' in user:
|
||||
up = u'languageCode'
|
||||
languages = [row[up] for row in user[u'languages'] if up in row]
|
||||
if languages:
|
||||
print u'Languages: %s' % u','.join(languages)
|
||||
up = u'customLanguage'
|
||||
languages = [row[up] for row in user[u'languages'] if up in row]
|
||||
if languages:
|
||||
print u'Custom Languages: %s' % u','.join(languages)
|
||||
if u'isAdmin' in user:
|
||||
print u'Is a Super Admin: %s' % user[u'isAdmin']
|
||||
if u'isDelegatedAdmin' in user:
|
||||
@ -8733,6 +8775,22 @@ def doGetUserInfo(user_email=None):
|
||||
else:
|
||||
print utils.convertUTF8(utils.indentMultiLineText(u' value: {0}'.format(notes), n=2))
|
||||
print u''
|
||||
if u'gender' in user:
|
||||
print u'Gender'
|
||||
gender = user[u'gender']
|
||||
for key in gender:
|
||||
if key == u'customGender' and not gender[key]:
|
||||
continue
|
||||
print utils.convertUTF8(u' %s: %s' % (key, gender[key]))
|
||||
print u''
|
||||
if u'keywords' in user:
|
||||
print u'Keywords:'
|
||||
for keyword in user[u'keywords']:
|
||||
for key in keyword:
|
||||
if key == u'customType' and not keyword[key]:
|
||||
continue
|
||||
print utils.convertUTF8(u' %s: %s' % (key, keyword[key]))
|
||||
print u''
|
||||
if u'ims' in user:
|
||||
print u'IMs:'
|
||||
for im in user[u'ims']:
|
||||
@ -9891,6 +9949,7 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
u'firstname': [u'name.givenName',],
|
||||
u'fullname': [u'name.fullName',],
|
||||
u'gal': [u'includeInGlobalAddressList',],
|
||||
u'gender': [u'gender.type', u'gender.customGender', u'gender.addressMeAs',],
|
||||
u'givenname': [u'name.givenName',],
|
||||
u'id': [u'id',],
|
||||
u'im': [u'ims',],
|
||||
@ -9904,6 +9963,10 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
u'is2svenforced': [u'isEnforcedIn2Sv',],
|
||||
u'is2svenrolled': [u'isEnrolledIn2Sv',],
|
||||
u'ismailboxsetup': [u'isMailboxSetup',],
|
||||
u'keyword': [u'keywords',],
|
||||
u'keywords': [u'keywords',],
|
||||
u'language': [u'languages',],
|
||||
u'languages': [u'languages',],
|
||||
u'lastlogintime': [u'lastLoginTime',],
|
||||
u'lastname': [u'name.familyName',],
|
||||
u'location': [u'locations',],
|
||||
|
30
src/var.py
30
src/var.py
@ -784,9 +784,10 @@ GAPI_MEMBERS_RETRY_REASONS = [GAPI_SYSTEM_ERROR]
|
||||
|
||||
USER_ADDRESS_TYPES = [u'home', u'work', u'other']
|
||||
USER_EMAIL_TYPES = [u'home', u'work', u'other']
|
||||
USER_EXTERNALID_TYPES = [u'account', u'customer', u'login_id', u'network',
|
||||
u'organization']
|
||||
USER_EXTERNALID_TYPES = [u'account', u'customer', u'login_id', u'network', u'organization']
|
||||
USER_GENDER_TYPES = [u'female', u'male', u'unknown']
|
||||
USER_IM_TYPES = [u'home', u'work', u'other']
|
||||
USER_KEYWORD_TYPES = [u'occupation', u'outlook']
|
||||
USER_LOCATION_TYPES = [u'default', u'desk']
|
||||
USER_ORGANIZATION_TYPES = [u'domain_only', u'school', u'unknown', u'work']
|
||||
USER_PHONE_TYPES = [u'assistant', u'callback', u'car', u'company_main',
|
||||
@ -952,3 +953,28 @@ WEBCOLOR_MAP = {
|
||||
u'yellow': u'#ffff00',
|
||||
u'yellowgreen': u'#9acd32',
|
||||
}
|
||||
# Valid language codes
|
||||
LANGUAGE_CODES_MAP = {
|
||||
u'ach': u'ach', u'af': u'af', u'ag': u'ga', u'ak': u'ak', u'am': u'am', u'ar': u'ar', u'az': u'az', #Luo, Afrikaans, Irish, Akan, Amharic, Arabica, Azerbaijani
|
||||
u'be': u'be', u'bem': u'bem', u'bg': u'bg', u'bn': u'bn', u'br': u'br', u'bs': u'bs', u'ca': u'ca', #Belarusian, Bemba, Bulgarian, Bengali, Breton, Bosnian, Catalan
|
||||
u'chr': u'chr', u'ckb': u'ckb', u'co': u'co', u'crs': u'crs', u'cs': u'cs', u'cy': u'cy', u'da': u'da', #Cherokee, Kurdish (Sorani), Corsican, Seychellois Creole, Czech, Welsh, Danish
|
||||
u'de': u'de', u'ee': u'ee', u'el': u'el', u'en': u'en', u'en-gb': u'en-GB', u'en-us': u'en-US', u'eo': u'eo', #German, Ewe, Greek, English, English (UK), English (US), Esperanto
|
||||
u'es': u'es', u'es-419': u'es-419', u'et': u'et', u'eu': u'eu', u'fa': u'fa', u'fi': u'fi', u'fo': u'fo', #Spanish, Spanish (Latin American), Estonian, Basque, Persian, Finnish, Faroese
|
||||
u'fr': u'fr', u'fr-ca': u'fr-ca', u'fy': u'fy', u'ga': u'ga', u'gaa': u'gaa', u'gd': u'gd', u'gl': u'gl', #French, French (Canada), Frisian, Irish, Ga, Scots Gaelic, Galician
|
||||
u'gn': u'gn', u'gu': u'gu', u'ha': u'ha', u'haw': u'haw', u'he': u'he', u'hi': u'hi', u'hr': u'hr', #Guarani, Gujarati, Hausa, Hawaiian, Hebrew, Hindi, Croatian
|
||||
u'ht': u'ht', u'hu': u'hu', u'hy': u'hy', u'ia': u'ia', u'id': u'id', u'ig': u'ig', u'in': u'in', #Haitian Creole, Hungarian, Armenian, Interlingua, Indonesian, Igbo, in
|
||||
u'is': u'is', u'it': u'it', u'iw': u'iw', u'ja': u'ja', u'jw': u'jw', u'ka': u'ka', u'kg': u'kg', #Icelandic, Italian, Hebrew, Japanese, Javanese, Georgian, Kongo
|
||||
u'kk': u'kk', u'km': u'km', u'kn': u'kn', u'ko': u'ko', u'kri': u'kri', u'ku': u'ku', u'ky': u'ky', #Kazakh, Khmer, Kannada, Korean, Krio (Sierra Leone), Kurdish, Kyrgyz
|
||||
u'la': u'la', u'lg': u'lg', u'ln': u'ln', u'lo': u'lo', u'loz': u'loz', u'lt': u'lt', u'lua': u'lua', #Latin, Luganda, Lingala, Laothian, Lozi, Lithuanian, Tshiluba
|
||||
u'lv': u'lv', u'mfe': u'mfe', u'mg': u'mg', u'mi': u'mi', u'mk': u'mk', u'ml': u'ml', u'mn': u'mn', #Latvian, Mauritian Creole, Malagasy, Maori, Macedonian, Malayalam, Mongolian
|
||||
u'mo': u'mo', u'mr': u'mr', u'ms': u'ms', u'mt': u'mt', u'my': u'my', u'ne': u'ne', u'nl': u'nl', #Moldavian, Marathi, Malay, Maltese, Burmese, Nepali, Dutch
|
||||
u'nn': u'nn', u'no': u'no', u'nso': u'nso', u'ny': u'ny', u'nyn': u'nyn', u'oc': u'oc', u'om': u'om', #Norwegian (Nynorsk), Norwegian, Northern Sotho, Chichewa, Runyakitara, Occitan, Oromo
|
||||
u'or': u'or', u'pa': u'pa', u'pcm': u'pcm', u'pl': u'pl', u'ps': u'ps', u'pt-br': u'pt-BR', u'pt-pt': u'pt-PT', #Oriya, Punjabi, Nigerian Pidgin, Polish, Pashto, Portuguese (Brazil), Portuguese (Portugal)
|
||||
u'qu': u'qu', u'rm': u'rm', u'rn': u'rn', u'ro': u'ro', u'ru': u'ru', u'rw': u'rw', u'sd': u'sd', #Quechua, Romansh, Kirundi, Romanian, Russian, Kinyarwanda, Sindhi
|
||||
u'sh': u'sh', u'si': u'si', u'sk': u'sk', u'sl': u'sl', u'sn': u'sn', u'so': u'so', u'sq': u'sq', #Serbo-Croatian, Sinhalese, Slovak, Slovenian, Shona, Somali, Albanian
|
||||
u'sr': u'sr', u'sr-me': u'sr-ME', u'st': u'st', u'su': u'su', u'sv': u'sv', u'sw': u'sw', u'ta': u'ta', #Serbian, Montenegrin, Sesotho, Sundanese, Swedish, Swahili, Tamil
|
||||
u'te': u'te', u'tg': u'tg', u'th': u'th', u'ti': u'ti', u'tk': u'tk', u'tl': u'tl', u'tn': u'tn', #Telugu, Tajik, Thai, Tigrinya, Turkmen, Tagalog, Setswana
|
||||
u'to': u'to', u'tr': u'tr', u'tt': u'tt', u'tum': u'tum', u'tw': u'tw', u'ug': u'ug', u'uk': u'uk', #Tonga, Turkish, Tatar, Tumbuka, Twi, Uighur, Ukrainian
|
||||
u'ur': u'ur', u'uz': u'uz', u'vi': u'vi', u'wo': u'wo', u'xh': u'xh', u'yi': u'yi', u'yo': u'yo', #Urdu, Uzbek, Vietnamese, Wolof, Xhosa, Yiddish, Yoruba
|
||||
u'zh-cn': u'zh-CN', u'zh-hk': u'zh-HK', u'zh-tw': u'zh-TW', u'zu': u'zu', #Chinese (Simplified), Chinese (Hong Kong/Traditional), Chinese (Taiwan/Traditional), Zulu
|
||||
}
|
||||
|
Reference in New Issue
Block a user