您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

342 行
9.8 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. """
  5. Contributing:
  6. 1. Add the .csv file
  7. 2. Run import
  8. 3. Then run translate
  9. """
  10. # loading
  11. # doctype, page, report
  12. # boot(startup)
  13. # frappe.require
  14. # frappe._
  15. import frappe, os, re, codecs, json
  16. def guess_language_from_http_header(lang):
  17. """set frappe.local.lang from HTTP headers at beginning of request"""
  18. if not lang:
  19. return frappe.local.lang
  20. guess = None
  21. lang_list = get_all_languages() or []
  22. if ";" in lang: # not considering weightage
  23. lang = lang.split(";")[0]
  24. if "," in lang:
  25. lang = lang.split(",")
  26. else:
  27. lang = [lang]
  28. for l in lang:
  29. code = l.strip()
  30. if code in lang_list:
  31. guess = code
  32. break
  33. # check if parent language (pt) is setup, if variant (pt-BR)
  34. if "-" in code:
  35. code = code.split("-")[0]
  36. if code in lang_list:
  37. guess = code
  38. break
  39. return guess or frappe.local.lang
  40. def get_user_lang(user=None):
  41. """set frappe.local.lang from user preferences on session beginning or resumption"""
  42. if not user:
  43. user = frappe.session.user
  44. # via cache
  45. lang = frappe.cache().get_value("lang:" + user)
  46. if not lang:
  47. # if defined in user profile
  48. user_lang = frappe.db.get_value("User", user, "language")
  49. if user_lang and user_lang!="Loading...":
  50. lang = get_lang_dict().get(user_lang)
  51. else:
  52. default_lang = frappe.db.get_default("lang")
  53. lang = default_lang or frappe.local.lang
  54. frappe.cache().set_value("lang:" + user, lang)
  55. return lang
  56. def set_default_language(language):
  57. lang = get_lang_dict()[language]
  58. frappe.db.set_default("lang", lang)
  59. frappe.local.lang = lang
  60. def get_all_languages():
  61. return [a.split()[0] for a in get_lang_info()]
  62. def get_lang_dict():
  63. return dict([[a[1], a[0]] for a in [a.split() for a in get_lang_info()]])
  64. def get_lang_info():
  65. return frappe.cache().get_value("langinfo",
  66. lambda:frappe.get_file_items(os.path.join(frappe.local.sites_path, "languages.txt")))
  67. def get_dict(fortype, name=None):
  68. fortype = fortype.lower()
  69. cache = frappe.cache()
  70. cache_key = "translation_assets:" + frappe.local.lang
  71. asset_key = fortype + ":" + (name or "-")
  72. translation_assets = cache.get_value(cache_key) or {}
  73. if not asset_key in translation_assets:
  74. if fortype=="doctype":
  75. messages = get_messages_from_doctype(name)
  76. elif fortype=="page":
  77. messages = get_messages_from_page(name)
  78. elif fortype=="report":
  79. messages = get_messages_from_report(name)
  80. elif fortype=="include":
  81. messages = get_messages_from_include_files()
  82. elif fortype=="jsfile":
  83. messages = get_messages_from_file(name)
  84. elif fortype=="boot":
  85. messages = get_messages_from_include_files()
  86. messages += frappe.db.sql_list("select name from tabDocType")
  87. messages += frappe.db.sql_list("select name from `tabModule Def`")
  88. translation_assets[asset_key] = make_dict_from_messages(messages)
  89. cache.set_value(cache_key, translation_assets)
  90. return translation_assets[asset_key]
  91. def add_lang_dict(code):
  92. messages = extract_messages_from_code(code)
  93. code += "\n\n$.extend(frappe._messages, %s)" % json.dumps(make_dict_from_messages(messages))
  94. return code
  95. def make_dict_from_messages(messages, full_dict=None):
  96. out = {}
  97. if full_dict==None:
  98. full_dict = get_full_dict(frappe.local.lang)
  99. for m in messages:
  100. if m in full_dict:
  101. out[m] = full_dict[m]
  102. return out
  103. def get_lang_js(fortype, name):
  104. return "\n\n$.extend(frappe._messages, %s)" % json.dumps(get_dict(fortype, name))
  105. def get_full_dict(lang):
  106. if lang == "en": return {}
  107. return frappe.cache().get_value("lang:" + lang, lambda:load_lang(lang))
  108. def load_lang(lang, apps=None):
  109. out = {}
  110. for app in (apps or frappe.get_all_apps(True)):
  111. path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv")
  112. if os.path.exists(path):
  113. cleaned = dict([item for item in dict(read_csv_file(path)).iteritems() if item[1]])
  114. out.update(cleaned)
  115. return out
  116. def clear_cache():
  117. cache = frappe.cache()
  118. cache.delete_value("langinfo")
  119. for lang in get_all_languages():
  120. cache.delete_value("lang:" + lang)
  121. cache.delete_value("translation_assets:" + lang)
  122. def get_messages_for_app(app):
  123. messages = []
  124. modules = ", ".join(['"{}"'.format(m.title().replace("_", " ")) \
  125. for m in frappe.local.app_modules[app]])
  126. # doctypes
  127. for name in frappe.db.sql_list("""select name from tabDocType
  128. where module in ({})""".format(modules)):
  129. messages.extend(get_messages_from_doctype(name))
  130. # pages
  131. for name, title in frappe.db.sql("""select name, title from tabPage
  132. where module in ({})""".format(modules)):
  133. messages.append(title or name)
  134. messages.extend(get_messages_from_page(name))
  135. # reports
  136. for name in frappe.db.sql_list("""select tabReport.name from tabDocType, tabReport
  137. where tabReport.ref_doctype = tabDocType.name
  138. and tabDocType.module in ({})""".format(modules)):
  139. messages.append(name)
  140. messages.extend(get_messages_from_report(name))
  141. # app_include_files
  142. messages.extend(get_messages_from_include_files(app))
  143. # server_messages
  144. messages.extend(get_server_messages(app))
  145. return list(set(messages))
  146. def get_messages_from_doctype(name):
  147. messages = []
  148. meta = frappe.get_meta(name)
  149. messages = [meta.name, meta.module]
  150. for d in meta.get("fields"):
  151. messages.extend([d.label, d.description])
  152. if d.fieldtype=='Select' and d.options \
  153. and not d.options.startswith("link:") \
  154. and not d.options.startswith("attach_files:"):
  155. options = d.options.split('\n')
  156. if not "icon" in options[0]:
  157. messages.extend(options)
  158. # extract from js, py files
  159. doctype_file_path = frappe.get_module_path(meta.module, "doctype", meta.name, meta.name)
  160. messages.extend(get_messages_from_file(doctype_file_path + ".js"))
  161. return clean(messages)
  162. def get_messages_from_page(name):
  163. return get_messages_from_page_or_report("Page", name)
  164. def get_messages_from_report(name):
  165. report = frappe.get_doc("Report", name)
  166. messages = get_messages_from_page_or_report("Report", name,
  167. frappe.db.get_value("DocType", report.ref_doctype, "module"))
  168. if report.query:
  169. messages.extend(re.findall('"([^:,^"]*):', report.query))
  170. messages.append(report.report_name)
  171. return clean(messages)
  172. def get_messages_from_page_or_report(doctype, name, module=None):
  173. if not module:
  174. module = frappe.db.get_value(doctype, name, "module")
  175. file_path = frappe.get_module_path(module, doctype, name, name)
  176. messages = get_messages_from_file(file_path + ".js")
  177. return clean(messages)
  178. def get_server_messages(app):
  179. messages = []
  180. for basepath, folders, files in os.walk(frappe.get_pymodule_path(app)):
  181. for dontwalk in (".git", "public", "locale"):
  182. if dontwalk in folders: folders.remove(dontwalk)
  183. for f in files:
  184. if f.endswith(".py") or f.endswith(".html"):
  185. messages.extend(get_messages_from_file(os.path.join(basepath, f)))
  186. return clean(messages)
  187. def get_messages_from_include_files(app_name=None):
  188. messages = []
  189. for file in (frappe.get_hooks("app_include_js") or []) + (frappe.get_hooks("web_include_js") or []):
  190. messages.extend(get_messages_from_file(os.path.join(frappe.local.sites_path, file)))
  191. return clean(messages)
  192. def get_messages_from_file(path):
  193. """get list of messages from a code file"""
  194. if os.path.exists(path):
  195. with open(path, 'r') as sourcefile:
  196. return extract_messages_from_code(sourcefile.read(), path.endswith(".py"))
  197. else:
  198. return []
  199. def extract_messages_from_code(code, is_py=False):
  200. messages = []
  201. messages += re.findall('_\("([^"]*)"', code)
  202. messages += re.findall("_\('([^']*)'", code)
  203. if is_py:
  204. messages += re.findall('_\("{3}([^"]*)"{3}.*\)', code, re.S)
  205. return clean(messages)
  206. def clean(messages):
  207. l = []
  208. messages = list(set(messages))
  209. for m in messages:
  210. if m:
  211. if re.search("[a-z]", m) and not m.startswith("icon-") and not m.endswith("px") and not m.startswith("eval:"):
  212. l.append(m)
  213. return l
  214. def read_csv_file(path):
  215. from csv import reader
  216. with codecs.open(path, 'r', 'utf-8') as msgfile:
  217. data = msgfile.read()
  218. data = reader([r.encode('utf-8') for r in data.splitlines()])
  219. newdata = [[unicode(val, 'utf-8') for val in row] for row in data]
  220. return newdata
  221. def write_csv_file(path, app_messages, lang_dict):
  222. app_messages.sort()
  223. from csv import writer
  224. with open(path, 'w') as msgfile:
  225. w = writer(msgfile)
  226. for m in app_messages:
  227. t = lang_dict.get(m, '')
  228. # strip whitespaces
  229. t = re.sub('{\s?([0-9]+)\s?}', "{\g<1>}", t)
  230. w.writerow([m.encode('utf-8'), t.encode('utf-8')])
  231. def get_untranslated(lang, untranslated_file, get_all=False):
  232. """translate objects using Google API. Add you own API key for translation"""
  233. clear_cache()
  234. apps = frappe.get_all_apps(True)
  235. messages = []
  236. untranslated = []
  237. for app in apps:
  238. messages.extend(get_messages_for_app(app))
  239. if get_all:
  240. print str(len(messages)) + " messages"
  241. with open(untranslated_file, "w") as f:
  242. for m in messages:
  243. f.write((m + "\n").encode("utf-8"))
  244. else:
  245. full_dict = get_full_dict(lang)
  246. for m in messages:
  247. if not full_dict.get(m):
  248. untranslated.append(m)
  249. if untranslated:
  250. print str(len(untranslated)) + " missing translations of " + str(len(messages))
  251. with open(untranslated_file, "w") as f:
  252. for m in untranslated:
  253. f.write((m + "\n").encode("utf-8"))
  254. else:
  255. print "all translated!"
  256. def update_translations(lang, untranslated_file, translated_file):
  257. clear_cache()
  258. full_dict = get_full_dict(lang)
  259. full_dict.update(dict(zip(frappe.get_file_items(untranslated_file),
  260. frappe.get_file_items(translated_file))))
  261. for app in frappe.get_all_apps(True):
  262. write_translations_file(app, lang, full_dict)
  263. def rebuild_all_translation_files():
  264. for lang in get_all_languages():
  265. for app in frappe.get_all_apps():
  266. write_translations_file(app, lang)
  267. def write_translations_file(app, lang, full_dict=None):
  268. tpath = frappe.get_pymodule_path(app, "translations")
  269. frappe.create_folder(tpath)
  270. write_csv_file(os.path.join(tpath, lang + ".csv"),
  271. get_messages_for_app(app), full_dict or get_full_dict(lang))