commit
224bf33f3d
27 changed files with 39560 additions and 36356 deletions
|
|
@ -1,11 +1,13 @@
|
|||
ar العربية
|
||||
de deutsch
|
||||
ca català
|
||||
el ελληνικά
|
||||
en english
|
||||
es español
|
||||
fr français
|
||||
hi हिंदी
|
||||
hr hrvatski
|
||||
bs bosanski
|
||||
is Íslenska
|
||||
id Indonesia
|
||||
it italiano
|
||||
|
|
@ -23,5 +25,5 @@ ta தமிழ்
|
|||
th ไทย
|
||||
tr Türk
|
||||
vi việt
|
||||
zh-cn 中文(中国)
|
||||
zh-tw 中文(臺灣)
|
||||
zh-cn 中国(简体)
|
||||
zh-tw 中國(繁體)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from __future__ import unicode_literals
|
|||
import frappe, os, re, codecs, json
|
||||
from frappe.utils.jinja import render_include
|
||||
from jinja2 import TemplateError
|
||||
import itertools, operator
|
||||
|
||||
def guess_language(lang_codes):
|
||||
"""Set `frappe.local.lang` from HTTP headers at beginning of request"""
|
||||
|
|
@ -134,6 +135,7 @@ def add_lang_dict(code):
|
|||
|
||||
:param code: Javascript code snippet to which translations needs to be appended."""
|
||||
messages = extract_messages_from_code(code)
|
||||
messages = [message for pos, message in messages]
|
||||
code += "\n\n$.extend(frappe._messages, %s)" % json.dumps(make_dict_from_messages(messages))
|
||||
return code
|
||||
|
||||
|
|
@ -147,9 +149,8 @@ def make_dict_from_messages(messages, full_dict=None):
|
|||
full_dict = get_full_dict(frappe.local.lang)
|
||||
|
||||
for m in messages:
|
||||
if m in full_dict:
|
||||
out[m] = full_dict[m]
|
||||
|
||||
if m[1] in full_dict:
|
||||
out[m[1]] = full_dict[m[1]]
|
||||
return out
|
||||
|
||||
def get_lang_js(fortype, name):
|
||||
|
|
@ -175,9 +176,9 @@ def load_lang(lang, apps=None):
|
|||
for app in (apps or frappe.get_all_apps(True)):
|
||||
path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv")
|
||||
if os.path.exists(path):
|
||||
cleaned = dict([item for item in dict(read_csv_file(path)).iteritems() if item[1]])
|
||||
cleaned = dict([(item[1], item[2]) for item in read_csv_file(path) if item[2]])
|
||||
# cleaned = dict([(item[0], item[1]) for item in read_csv_file(path) if item[1]])
|
||||
out.update(cleaned)
|
||||
|
||||
return out
|
||||
|
||||
def clear_cache():
|
||||
|
|
@ -203,23 +204,26 @@ def get_messages_for_app(app):
|
|||
# pages
|
||||
for name, title in frappe.db.sql("""select name, title from tabPage
|
||||
where module in ({})""".format(modules)):
|
||||
messages.append(title or name)
|
||||
messages.append((None, title or name))
|
||||
messages.extend(get_messages_from_page(name))
|
||||
|
||||
|
||||
# reports
|
||||
for name in frappe.db.sql_list("""select tabReport.name from tabDocType, tabReport
|
||||
where tabReport.ref_doctype = tabDocType.name
|
||||
and tabDocType.module in ({})""".format(modules)):
|
||||
messages.append(name)
|
||||
messages.append((None, name))
|
||||
messages.extend(get_messages_from_report(name))
|
||||
for i in messages:
|
||||
if not isinstance(i, tuple):
|
||||
raise Exception
|
||||
|
||||
# app_include_files
|
||||
messages.extend(get_messages_from_include_files(app))
|
||||
|
||||
# server_messages
|
||||
messages.extend(get_server_messages(app))
|
||||
|
||||
return list(set(messages))
|
||||
return deduplicate_messages(messages)
|
||||
|
||||
def get_messages_from_doctype(name):
|
||||
"""Extract all translatable messages for a doctype. Includes labels, Python code,
|
||||
|
|
@ -246,13 +250,16 @@ def get_messages_from_doctype(name):
|
|||
if d.role:
|
||||
messages.append(d.role)
|
||||
|
||||
messages = [message for message in messages if message]
|
||||
messages = [('DocType: ' + name, message) for message in messages if is_translatable(message)]
|
||||
|
||||
# extract from js, py files
|
||||
doctype_file_path = frappe.get_module_path(meta.module, "doctype", meta.name, meta.name)
|
||||
messages.extend(get_messages_from_file(doctype_file_path + ".js"))
|
||||
messages.extend(get_messages_from_file(doctype_file_path + "_list.js"))
|
||||
messages.extend(get_messages_from_file(doctype_file_path + "_list.html"))
|
||||
messages.extend(get_messages_from_file(doctype_file_path + "_calendar.js"))
|
||||
return clean(messages)
|
||||
return messages
|
||||
|
||||
def get_messages_from_page(name):
|
||||
"""Returns all translatable strings from a :class:`frappe.core.doctype.Page`"""
|
||||
|
|
@ -264,10 +271,11 @@ def get_messages_from_report(name):
|
|||
report = frappe.get_doc("Report", name)
|
||||
messages = _get_messages_from_page_or_report("Report", name,
|
||||
frappe.db.get_value("DocType", report.ref_doctype, "module"))
|
||||
# TODO position here!
|
||||
if report.query:
|
||||
messages.extend(re.findall('"([^:,^"]*):', report.query))
|
||||
messages.append(report.report_name)
|
||||
return clean(messages)
|
||||
messages.extend([(None, message) for message in re.findall('"([^:,^"]*):', report.query) if is_translatable(message)])
|
||||
messages.append((None,report.report_name))
|
||||
return messages
|
||||
|
||||
def _get_messages_from_page_or_report(doctype, name, module=None):
|
||||
if not module:
|
||||
|
|
@ -281,7 +289,7 @@ def _get_messages_from_page_or_report(doctype, name, module=None):
|
|||
if filename.endswith(".js") or filename.endswith(".html"):
|
||||
messages += get_messages_from_file(os.path.join(doc_path, filename))
|
||||
|
||||
return clean(messages)
|
||||
return messages
|
||||
|
||||
def get_server_messages(app):
|
||||
"""Extracts all translatable strings (tagged with :func:`frappe._`) from Python modules inside an app"""
|
||||
|
|
@ -294,7 +302,7 @@ def get_server_messages(app):
|
|||
if f.endswith(".py") or f.endswith(".html") or f.endswith(".js"):
|
||||
messages.extend(get_messages_from_file(os.path.join(basepath, f)))
|
||||
|
||||
return clean(messages)
|
||||
return messages
|
||||
|
||||
def get_messages_from_include_files(app_name=None):
|
||||
"""Extracts all translatable strings from Javascript app files"""
|
||||
|
|
@ -302,16 +310,18 @@ def get_messages_from_include_files(app_name=None):
|
|||
for file in (frappe.get_hooks("app_include_js", app_name=app_name) or []) + (frappe.get_hooks("web_include_js", app_name=app_name) or []):
|
||||
messages.extend(get_messages_from_file(os.path.join(frappe.local.sites_path, file)))
|
||||
|
||||
return clean(messages)
|
||||
return messages
|
||||
|
||||
def get_messages_from_file(path):
|
||||
"""Returns a list of transatable strings from a code file
|
||||
|
||||
:param path: path of the code file
|
||||
"""
|
||||
apps_path = get_bench_dir()
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as sourcefile:
|
||||
return extract_messages_from_code(sourcefile.read(), path.endswith(".py"))
|
||||
return [(os.path.relpath(" +".join([path, str(pos)]), apps_path),
|
||||
message) for pos, message in extract_messages_from_code(sourcefile.read(), path.endswith(".py"))]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
|
@ -327,23 +337,31 @@ def extract_messages_from_code(code, is_py=False):
|
|||
pass
|
||||
|
||||
messages = []
|
||||
messages += re.findall('_\("([^"]*)"', code)
|
||||
messages += re.findall("_\('([^']*)'", code)
|
||||
messages += [(m.start(), m.groups()[0]) for m in re.compile('_\("([^"]*)"').finditer(code)]
|
||||
messages += [(m.start(), m.groups()[0]) for m in re.compile("_\('([^']*)'").finditer(code)]
|
||||
if is_py:
|
||||
messages += re.findall('_\("{3}([^"]*)"{3}.*\)', code, re.S)
|
||||
messages += [(m.start(), m.groups()[0]) for m in re.compile('_\("{3}([^"]*)"{3}.*\)').finditer(code)]
|
||||
messages = [(pos, message) for pos, message in messages if is_translatable(message)]
|
||||
return pos_to_line_no(messages, code)
|
||||
|
||||
return clean(messages)
|
||||
|
||||
def clean(messages):
|
||||
"""Scrub and return list of translatable messages. Strips empty strings, numbers, known CSS classes etc."""
|
||||
l = []
|
||||
messages = list(set(messages))
|
||||
for m in messages:
|
||||
if m:
|
||||
if re.search("[a-z]", m) and not m.startswith("icon-") and not m.endswith("px") and not m.startswith("eval:"):
|
||||
l.append(m)
|
||||
return l
|
||||
def is_translatable(m):
|
||||
if re.search("[a-z]", m) and not m.startswith("icon-") and not m.endswith("px") and not m.startswith("eval:"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def pos_to_line_no(messages, code):
|
||||
ret = []
|
||||
messages = sorted(messages, key=lambda x: x[0])
|
||||
newlines = [m.start() for m in re.compile('\\n').finditer(code)]
|
||||
line = 1
|
||||
newline_i = 0
|
||||
for pos, message in messages:
|
||||
while newline_i < len(newlines) and pos > newlines[newline_i]:
|
||||
line+=1
|
||||
newline_i+= 1
|
||||
ret.append((line, message))
|
||||
return ret
|
||||
|
||||
def read_csv_file(path):
|
||||
"""Read CSV file and return as list of list
|
||||
|
||||
|
|
@ -367,13 +385,13 @@ def write_csv_file(path, app_messages, lang_dict):
|
|||
"""
|
||||
app_messages.sort()
|
||||
from csv import writer
|
||||
with open(path, 'w') as msgfile:
|
||||
w = writer(msgfile)
|
||||
for m in app_messages:
|
||||
with open(path, 'wb') as msgfile:
|
||||
w = writer(msgfile, lineterminator='\n')
|
||||
for p, m in app_messages:
|
||||
t = lang_dict.get(m, '')
|
||||
# strip whitespaces
|
||||
t = re.sub('{\s?([0-9]+)\s?}', "{\g<1>}", t)
|
||||
w.writerow([m.encode('utf-8'), t.encode('utf-8')])
|
||||
w.writerow([p.encode('utf-8') if p else '', m.encode('utf-8'), t.encode('utf-8')])
|
||||
|
||||
def get_untranslated(lang, untranslated_file, get_all=False):
|
||||
"""Returns all untranslated strings for a language and writes in a file
|
||||
|
|
@ -389,7 +407,7 @@ def get_untranslated(lang, untranslated_file, get_all=False):
|
|||
for app in apps:
|
||||
messages.extend(get_messages_for_app(app))
|
||||
|
||||
messages = list(set(messages))
|
||||
messages = deduplicate_messages(messages)
|
||||
|
||||
def escape_newlines(s):
|
||||
return (s.replace("\\\n", "|||||")
|
||||
|
|
@ -401,13 +419,13 @@ def get_untranslated(lang, untranslated_file, get_all=False):
|
|||
with open(untranslated_file, "w") as f:
|
||||
for m in messages:
|
||||
# replace \n with ||| so that internal linebreaks don't get split
|
||||
f.write((escape_newlines(m) + os.linesep).encode("utf-8"))
|
||||
f.write((escape_newlines(m[1]) + os.linesep).encode("utf-8"))
|
||||
else:
|
||||
full_dict = get_full_dict(lang)
|
||||
|
||||
for m in messages:
|
||||
if not full_dict.get(m):
|
||||
untranslated.append(m)
|
||||
if not full_dict.get(m[1]):
|
||||
untranslated.append(m[1])
|
||||
|
||||
if untranslated:
|
||||
print str(len(untranslated)) + " missing translations of " + str(len(messages))
|
||||
|
|
@ -478,3 +496,14 @@ def send_translations(translation_dict):
|
|||
frappe.local.response["__messages"] = {}
|
||||
|
||||
frappe.local.response["__messages"].update(translation_dict)
|
||||
|
||||
def deduplicate_messages(messages):
|
||||
ret = []
|
||||
op = operator.itemgetter(1)
|
||||
messages = sorted(messages, key=op)
|
||||
for k, g in itertools.groupby(messages, op):
|
||||
ret.append(g.next())
|
||||
return ret
|
||||
|
||||
def get_bench_dir():
|
||||
return os.path.join(frappe.__file__, '..', '..', '..', '..')
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue