#
# Jasy - Web Tooling Framework
# Copyright 2010-2012 Zynga Inc.
# Copyright 2013-2014 Sebastian Werner
#
import re
import copy
import polib
import jasy.script.parse.Node as Node
import jasy.item.Translation as Translation
from jasy import UserError
import jasy.core.Console as Console
#
# Public API
#
__all__ = ("hasText", "optimize", "collectTranslations")
translationFunctions = ("tr", "trc", "trn", "marktr")
[docs]def hasText(node):
if node.type == "call":
funcName = None
if node[0].type == "identifier":
funcName = node[0].value
elif node[0].type == "dot" and node[0][1].type == "identifier":
funcName = node[0][1].value
if funcName in translationFunctions:
return True
# Process children
for child in node:
if child is not None:
ret = hasText(child)
if ret:
return True
return False
def parseParams(params, funcName):
basic = None
plural = None
context = None
if funcName == "tr" or funcName == "trn" or funcName == "marktr":
basic = params[0].value
elif funcName == "trc":
context = params[0].value
basic = params[1].value
if funcName == "trn":
plural = params[1].value
return basic, plural, context
def __collectionRecurser(node, collection):
if node.type == "call":
funcName = None
if node[0].type == "identifier":
funcName = node[0].value
elif node[0].type == "dot" and node[0][1].type == "identifier":
funcName = node[0][1].value
if funcName in translationFunctions:
translationId = Translation.generateId(*parseParams(node[1], funcName))
if translationId:
if translationId in collection:
collection[translationId].append(node.line)
else:
collection[translationId] = [node.line]
# Process children
for child in node:
if child is not None:
__collectionRecurser(child, collection)
return collection
[docs]def collectTranslations(node):
return __collectionRecurser(node, dict())
[docs]def optimize(node, translation):
return __recurser(node, translation.getTable())
#
# Patch :: Implementation
#
__replacer = re.compile("(%[0-9])")
def __splitTemplate(value, valueParams):
"""
Split string into plus-expression(s)
- patchParam: string node containing the placeholders
- valueParams: list of params to inject
"""
# Convert list with nodes into Python dict
# [a, b, c] => {0:a, 1:b, 2:c}
mapper = {pos: value for pos, value in enumerate(valueParams)}
result = []
splits = __replacer.split(value)
if len(splits) == 1:
return None
pair = Node.Node(None, "plus")
for entry in splits:
if entry == "":
continue
if len(pair) == 2:
newPair = Node.Node(None, "plus")
newPair.append(pair)
pair = newPair
if __replacer.match(entry):
pos = int(entry[1]) - 1
# Items might be added multiple times. Copy to protect original.
try:
repl = mapper[pos]
except KeyError:
raise UserError("Invalid positional value: %s in %s" % (entry, value))
copied = copy.deepcopy(mapper[pos])
if copied.type not in ("identifier", "call"):
copied.parenthesized = True
pair.append(copied)
else:
child = Node.Node(None, "string")
child.value = entry
pair.append(child)
return pair
def __recurser(node, table):
counter = 0
# Process children
for child in list(node):
if child is not None:
counter += __recurser(child, table)
# Process all method calls
if node.type == "call":
funcName = None
funcNameNode = None
# Uses global translation method (not typical)
if node[0].type == "identifier":
funcNameNode = node[0]
# Uses namespaced translation method.
# Typically core.locale.Translation.tr() or Translation.tr()
elif node[0].type == "dot" and node[0][1].type == "identifier":
funcNameNode = node[0][1]
# Gettext methods only at the moment
funcName = funcNameNode and funcNameNode.value
if funcName in translationFunctions:
Console.debug("Found translation method %s in %s", funcName, node.line)
Console.indent()
params = node[1]
# Remove marktr() calls
if funcName == "marktr":
node.parent.remove(node)
# Verify param types
elif params[0].type is not "string":
# maybe something marktr() relevant being used, in this case we need to keep the call and inline the data
pass
# Error handling
elif (funcName == "trn" or funcName == "trc") and params[1].type != "string":
Console.warn("Expecting translation string to be type string: %s at line %s" % (params[1].type, params[1].line))
# Signature tr(msg, arg1, ...)
elif funcName == "tr":
key = params[0].value
if key in table:
params[0].value = table[key]
counter += 1
if len(params) == 1:
node.parent.replace(node, params[0])
else:
replacement = __splitTemplate(params[0].value, params[1:])
if replacement:
node.parent.replace(node, replacement)
# Signature trc(context, msg, arg1, ...)
elif funcName == "trc":
key = "%s[C:%s]" % (params[1].value, params[0].value)
if key in table:
params[1].value = table[key]
counter += 1
if len(params) == 2:
node.parent.replace(node, params[1])
else:
replacement = __splitTemplate(params[1].value, params[2:])
if replacement:
node.parent.replace(node, replacement)
# Signature trn(msgSingular, msgPlural, int, arg1, ...)
elif funcName == "trn":
key = "%s[N:%s]" % (params[0].value, params[1].value)
if key not in table:
Console.outdent()
return counter
counter += 1
# Use optimized trnc() method instead of trn()
funcNameNode.value = "trnc"
# Remove first two string parameters
params.remove(params[0])
params.remove(params[0])
# Inject new object into params
container = Node.Node(None, "object_init")
params.insert(0, container)
# Create new construction with all properties generated from the translation table
for plural in table[key]:
pluralEntry = Node.Node(None, "property_init")
pluralEntryIdentifier = Node.Node(None, "identifier")
pluralEntryIdentifier.value = plural
pluralEntryValue = Node.Node(None, "string")
pluralEntryValue.value = table[key][plural]
pluralEntry.append(pluralEntryIdentifier)
pluralEntry.append(pluralEntryValue)
container.append(pluralEntry)
# Replace strings with plus operations to omit complex client side string operation
if len(params) > 2:
for pluralEntry in container:
replacement = __splitTemplate(pluralEntry[1].value, params[2:])
if replacement:
pluralEntry.replace(pluralEntry[1], replacement)
# When all variables have been patched in all string with placeholder
# we are able to remove the whole list of placeholder values afterwards
while len(params) > 2:
params.pop()
Console.outdent()
return counter