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.
 
 
 
 
 
 

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