You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

444 lines
13 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  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. import webnotes
  11. import os
  12. import codecs
  13. import json
  14. import re
  15. from csv import reader
  16. from webnotes.modules import get_doc_path,get_doctype_module
  17. from webnotes.utils import get_base_path, cstr
  18. messages = {}
  19. def translate(lang=None):
  20. languages = [lang]
  21. if lang=="all" or lang==None:
  22. languages = get_all_languages()
  23. print "Extracting / updating translatable strings..."
  24. build_message_files()
  25. print "Compiling messages in one file..."
  26. export_messages(lang, '_lang_tmp.csv')
  27. for lang in languages:
  28. if lang != "en":
  29. filename = 'app/translations/'+lang+'.csv'
  30. print "For " + lang + ":"
  31. print "Translating via Google Translate..."
  32. google_translate(lang, '_lang_tmp.csv', filename)
  33. print "Updating language files..."
  34. import_messages(lang, filename)
  35. print "Deleting temp file..."
  36. os.remove('_lang_tmp.csv')
  37. def get_all_languages():
  38. try:
  39. return [f[:-4] for f in os.listdir("app/translations") if f.endswith(".csv")]
  40. except OSError, e:
  41. if e.args[0]==2:
  42. return []
  43. else:
  44. raise e
  45. def get_lang_dict():
  46. languages_path = os.path.join(get_base_path(), "app", "translations", "languages.json")
  47. if os.path.exists(languages_path):
  48. with open(languages_path, "r") as langfile:
  49. return json.loads(langfile.read())
  50. else: return {}
  51. def update_translations():
  52. """
  53. compare language file timestamps with last updated timestamps in `.wnf-lang-status`
  54. if timestamps are missing / changed, build new `.json` files in the `lang folders`
  55. """
  56. langstatus = {}
  57. languages = get_all_languages()
  58. message_updated = False
  59. status_file_path = "app/.wnf-lang-status"
  60. if not os.path.exists(os.path.join('app', 'translations')):
  61. return
  62. if os.path.exists(status_file_path):
  63. with open(status_file_path, "r") as langstatusfile:
  64. langstatus = eval(langstatusfile.read())
  65. for lang in languages:
  66. filename = os.path.join('app', 'translations', lang + '.csv')
  67. if langstatus.get(lang, None)!=os.path.getmtime(filename):
  68. print "Setting up lang files for " + lang + "..."
  69. if not message_updated:
  70. print "Extracting / updating translatable strings..."
  71. build_message_files()
  72. message_updated = True
  73. print "Writing translations..."
  74. import_messages(lang, filename)
  75. langstatus[lang] = os.path.getmtime(filename)
  76. with open(status_file_path, "w") as langstatusfile:
  77. langstatus = langstatusfile.write(str(langstatus))
  78. def build_message_files():
  79. """build from doctypes, pages, database and framework"""
  80. if not webnotes.conn:
  81. webnotes.connect()
  82. build_for_pages('lib/core')
  83. build_for_pages('app')
  84. build_from_doctype_code('lib/core')
  85. build_from_doctype_code('app')
  86. #reports
  87. build_from_query_report()
  88. # doctype
  89. build_from_database()
  90. build_for_framework('lib/webnotes', 'py', with_doctype_names=True)
  91. build_for_framework('lib/public/js/wn', 'js')
  92. build_for_framework('app/public/js', 'js', with_doctype_names=True)
  93. def build_for_pages(path):
  94. """make locale files for framework py and js (all)"""
  95. messages = []
  96. for (basepath, folders, files) in os.walk(path):
  97. if os.path.basename(os.path.dirname(basepath))=="page":
  98. messages_js, messages_py = [], []
  99. for fname in files:
  100. fname = cstr(fname)
  101. if fname.endswith('.js'):
  102. messages_js += get_message_list(os.path.join(basepath, fname))
  103. if fname.endswith('.py'):
  104. messages_py += get_message_list(os.path.join(basepath, fname))
  105. if messages_js:
  106. write_messages_file(basepath, messages_js, "js")
  107. if messages_py:
  108. write_messages_file(basepath, messages_py, "py")
  109. def build_from_query_report():
  110. """make locale for the query reports from database and the framework js and py files"""
  111. import re
  112. for item in webnotes.conn.sql("""select report_name,ref_doctype,query from `tabReport`""", as_dict=1):
  113. messages_js, messages_py = [], []
  114. if item:
  115. messages_js.append(item.report_name)
  116. messages_py.append(item.report_name)
  117. # get the messages from the query using the regex :
  118. # if we have the string "Production Date:Date:180" in the query then the regex will search for string between " and : .
  119. # the regex will take "Production Date" and store them into messages
  120. if item.query :
  121. messages_query = re.findall('"([^:,^"]*):', item.query)
  122. messages_js += messages_query
  123. messages_py += messages_query
  124. module = get_doctype_module(item.ref_doctype)
  125. if module :
  126. doctype_path = get_doc_path(module, "Report", item.report_name)
  127. if os.path.exists(doctype_path):
  128. for (basepath, folders, files) in os.walk(doctype_path):
  129. for fname in files:
  130. if fname.endswith('.js'):
  131. messages_js += get_message_list(os.path.join(basepath, fname))
  132. if fname.endswith('.py'):
  133. messages_py += get_message_list(os.path.join(basepath, fname))
  134. break
  135. write_messages_file(doctype_path, messages_js, 'js')
  136. write_messages_file(doctype_path, messages_py, 'py')
  137. def build_from_database():
  138. """make doctype labels, names, options, descriptions"""
  139. def get_select_options(doc):
  140. if doc.doctype=="DocField" and doc.fieldtype=='Select' and doc.options \
  141. and not doc.options.startswith("link:") \
  142. and not doc.options.startswith("attach_files:"):
  143. return doc.options.split('\n')
  144. else:
  145. return []
  146. build_for_doc_from_database(webnotes._dict({
  147. "doctype": "DocType",
  148. "module_field": "module",
  149. "DocType": ["name", "description", "module"],
  150. "DocField": ["label", "description"],
  151. "custom": get_select_options
  152. }))
  153. def build_for_doc_from_database(fields):
  154. for item in webnotes.conn.sql("""select name from `tab%s`""" % fields.doctype, as_dict=1):
  155. messages = []
  156. doclist = webnotes.bean(fields.doctype, item.name).doclist
  157. for doc in doclist:
  158. if doc.doctype in fields:
  159. messages += map(lambda x: x in fields[doc.doctype] and doc.fields.get(x) or None,
  160. doc.fields.keys())
  161. if fields.custom:
  162. messages += fields.custom(doc)
  163. doc = doclist[0]
  164. if doc.fields.get(fields.module_field):
  165. doctype_path = get_doc_path(doc.fields[fields.module_field],
  166. doc.doctype, doc.name)
  167. write_messages_file(doctype_path, messages, 'doc')
  168. def build_for_framework(path, mtype, with_doctype_names = False):
  169. """make locale files for framework py and js (all)"""
  170. messages = []
  171. for (basepath, folders, files) in os.walk(path):
  172. for fname in files:
  173. fname = cstr(fname)
  174. if fname.endswith('.' + mtype):
  175. messages += get_message_list(os.path.join(basepath, fname))
  176. # append module & doctype names
  177. if with_doctype_names:
  178. for m in webnotes.conn.sql("""select name, module from `tabDocType`"""):
  179. messages.append(m[0])
  180. messages.append(m[1])
  181. # append labels from config.json
  182. config = webnotes.get_config()
  183. for moduleinfo in config["modules"].values():
  184. if moduleinfo.get("label"):
  185. messages.append(moduleinfo["label"])
  186. if messages:
  187. write_messages_file(path, messages, mtype)
  188. def build_from_doctype_code(path):
  189. """walk and make locale files in all folders"""
  190. for (basepath, folders, files) in os.walk(path):
  191. messagespy = []
  192. messagesjs = []
  193. for fname in files:
  194. fname = cstr(fname)
  195. if fname.endswith('py'):
  196. messagespy += get_message_list(os.path.join(basepath, fname))
  197. if fname.endswith('js'):
  198. messagesjs += get_message_list(os.path.join(basepath, fname))
  199. if messagespy:
  200. write_messages_file(basepath, messagespy, 'py')
  201. if messagespy:
  202. write_messages_file(basepath, messagesjs, 'js')
  203. def get_message_list(path):
  204. """get list of messages from a code file"""
  205. import re
  206. messages = []
  207. with open(path, 'r') as sourcefile:
  208. txt = sourcefile.read()
  209. messages += re.findall('_\("([^"]*)"\)', txt)
  210. messages += re.findall("_\('([^']*)'\)", txt)
  211. messages += re.findall('_\("{3}([^"]*)"{3}\)', txt, re.S)
  212. return messages
  213. def write_messages_file(path, messages, mtype):
  214. """write messages to translation file"""
  215. if not os.path.exists(path):
  216. return
  217. if not os.path.exists(os.path.join(path, 'locale')):
  218. os.makedirs(os.path.join(path, 'locale'))
  219. fname = os.path.join(path, 'locale', '_messages_' + mtype + '.json')
  220. messages = list(set(messages))
  221. filtered = []
  222. for m in messages:
  223. if m and re.search('[a-zA-Z]+', m):
  224. filtered.append(m)
  225. with open(fname, 'w') as msgfile:
  226. msgfile.write(json.dumps(filtered, indent=1))
  227. def export_messages(lang, outfile):
  228. """get list of all messages"""
  229. messages = {}
  230. # extract messages
  231. for (basepath, folders, files) in os.walk('.'):
  232. def _get_messages(messages, basepath, mtype):
  233. mlist = get_messages(basepath, mtype)
  234. if not mlist:
  235. return
  236. # update messages with already existing translations
  237. langdata = get_lang_data(basepath, lang, mtype)
  238. for m in mlist:
  239. if not messages.get(m):
  240. messages[m] = langdata.get(m, "")
  241. if os.path.basename(basepath)=='locale':
  242. _get_messages(messages, basepath, 'doc')
  243. _get_messages(messages, basepath, 'py')
  244. _get_messages(messages, basepath, 'js')
  245. # remove duplicates
  246. if outfile:
  247. from csv import writer
  248. with open(outfile, 'w') as msgfile:
  249. w = writer(msgfile)
  250. keys = messages.keys()
  251. keys.sort()
  252. for m in keys:
  253. w.writerow([m.encode('utf-8'), messages.get(m, '').encode('utf-8')])
  254. def import_messages(lang, infile):
  255. """make individual message files for each language"""
  256. data = dict(get_all_messages_from_file(infile))
  257. for (basepath, folders, files) in os.walk('.'):
  258. def _update_lang_file(mtype):
  259. """create a langauge file for the given message type"""
  260. messages = get_messages(basepath, mtype)
  261. if not messages: return
  262. # read existing
  263. langdata = get_lang_data(basepath, lang, mtype)
  264. # update fresh
  265. for m in messages:
  266. if data.get(m):
  267. langdata[m] = data.get(m)
  268. if langdata:
  269. # write new langfile
  270. langfilename = os.path.join(basepath, lang + '-' + mtype + '.json')
  271. with open(langfilename, 'w') as langfile:
  272. langfile.write(json.dumps(langdata, indent=1, sort_keys=True).encode('utf-8'))
  273. #print 'wrote ' + langfilename
  274. if os.path.basename(basepath)=='locale':
  275. # make / update lang files for each type of message file (doc, js, py)
  276. # example: hi-doc.json, hi-js.json, hi-py.json
  277. _update_lang_file('doc')
  278. _update_lang_file('js')
  279. _update_lang_file('py')
  280. docs_loaded = []
  281. def load_doc_messages(module, doctype, name):
  282. if webnotes.lang=="en":
  283. return {}
  284. global docs_loaded
  285. doc_path = get_doc_path(module, doctype, name)
  286. # don't repload the same doc again
  287. if (webnotes.lang + ":" + doc_path) in docs_loaded:
  288. return
  289. docs_loaded.append(webnotes.lang + ":" + doc_path)
  290. global messages
  291. messages.update(get_lang_data(doc_path, None, 'doc'))
  292. def get_lang_data(basepath, lang, mtype):
  293. """get language dict from langfile"""
  294. # add "locale" folder if reqd
  295. if os.path.basename(basepath) != 'locale':
  296. basepath = os.path.join(basepath, 'locale')
  297. if not lang: lang = webnotes.lang
  298. path = os.path.join(basepath, lang + '-' + mtype + '.json')
  299. langdata = {}
  300. if os.path.exists(path):
  301. with codecs.open(path, 'r', 'utf-8') as langfile:
  302. langdata = json.loads(langfile.read())
  303. return langdata
  304. def get_messages(basepath, mtype):
  305. """load list of messages from _message files"""
  306. # get message list
  307. path = os.path.join(basepath, '_messages_' + mtype + '.json')
  308. messages = []
  309. if os.path.exists(path):
  310. with open(path, 'r') as msgfile:
  311. messages = json.loads(msgfile.read())
  312. return messages
  313. def update_lang_js(jscode, path):
  314. return jscode + "\n\n$.extend(wn._messages, %s)" % \
  315. json.dumps(get_lang_data(path, webnotes.lang, 'js'))
  316. def get_all_messages_from_file(path):
  317. with codecs.open(path, 'r', 'utf-8') as msgfile:
  318. data = msgfile.read()
  319. data = reader([r.encode('utf-8') for r in data.splitlines()])
  320. newdata = []
  321. for row in data:
  322. newrow = []
  323. for val in row:
  324. newrow.append(unicode(val, 'utf-8'))
  325. newdata.append(newrow)
  326. return newdata
  327. def google_translate(lang, infile, outfile):
  328. """translate objects using Google API. Add you own API key for translation"""
  329. data = get_all_messages_from_file(infile)
  330. import requests, conf
  331. old_translations = {}
  332. # update existing translations
  333. if os.path.exists(outfile):
  334. with codecs.open(outfile, "r", "utf-8") as oldfile:
  335. old_data = oldfile.read()
  336. old_translations = dict(reader([r.encode('utf-8').strip() for r in old_data.splitlines()]))
  337. with open(outfile, 'w') as msgfile:
  338. from csv import writer
  339. w = writer(msgfile)
  340. for row in data:
  341. if row[0] and row[0].strip():
  342. if old_translations.get(row[0].strip()):
  343. row[1] = old_translations[row[0].strip()]
  344. else:
  345. print 'translating: ' + row[0]
  346. response = requests.get("""https://www.googleapis.com/language/translate/v2""",
  347. params = {
  348. "key": conf.google_api_key,
  349. "source": "en",
  350. "target": lang,
  351. "q": row[0]
  352. })
  353. if "error" in response.json:
  354. print response.json
  355. continue
  356. row[1] = response.json["data"]["translations"][0]["translatedText"]
  357. if not row[1]:
  358. row[1] = row[0] # google unable to translate!
  359. row[1] = row[1].encode('utf-8')
  360. row[0] = row[0].encode('utf-8')
  361. w.writerow(row)