diff --git a/addons/web/common/http.py b/addons/web/common/http.py index 434edf5e3a7..ac9b95dcb26 100644 --- a/addons/web/common/http.py +++ b/addons/web/common/http.py @@ -3,6 +3,7 @@ # OpenERP Web HTTP layer #---------------------------------------------------------- import ast +import cgi import contextlib import functools import logging @@ -92,7 +93,9 @@ class WebRequest(object): self.params = dict(params) # OpenERP session setup self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex - self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession()) + self.session = self.httpsession.get(self.session_id) + if not self.session: + self.httpsession[self.session_id] = self.session = session.OpenERPSession() self.session.config = self.config self.context = self.params.pop('context', None) self.debug = self.params.pop('debug', False) != False @@ -252,7 +255,30 @@ class HttpRequest(WebRequest): else: akw[key] = type(value) _logger.debug("%s --> %s.%s %r", self.httprequest.method, controller.__class__.__name__, method.__name__, akw) - r = method(controller, self, **self.params) + try: + r = method(controller, self, **self.params) + except xmlrpclib.Fault, e: + r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({ + 'code': 200, + 'message': "OpenERP Server Error", + 'data': { + 'type': 'server_exception', + 'fault_code': e.faultCode, + 'debug': "Server %s\nClient %s" % ( + e.faultString, traceback.format_exc()) + } + }))) + except Exception: + logging.getLogger(__name__ + '.HttpRequest.dispatch').exception( + "An error occurred while handling a json request") + r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({ + 'code': 300, + 'message': "OpenERP WebClient Error", + 'data': { + 'type': 'client_exception', + 'debug': "Client %s" % traceback.format_exc() + } + }))) if self.debug or 1: if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)): _logger.debug('<-- %s', r) diff --git a/addons/web/common/session.py b/addons/web/common/session.py index ca23a08da4a..24564ba7bf8 100644 --- a/addons/web/common/session.py +++ b/addons/web/common/session.py @@ -80,6 +80,13 @@ class OpenERPSession(object): """ self.build_connection().check_login(force) + def ensure_valid(self): + if self._uid: + try: + self.assert_valid(True) + except Exception: + self._uid = None + def execute(self, model, func, *l, **d): self.assert_valid() model = self.build_connection().get_model(model) diff --git a/addons/web/controllers/main.html b/addons/web/controllers/main.html new file mode 100644 index 00000000000..fd0be5ba349 --- /dev/null +++ b/addons/web/controllers/main.html @@ -0,0 +1,21 @@ + + + + + OpenERP + + %(css)s + %(js)s + + + + + + \ No newline at end of file diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index a514cafb6c8..7f5b43ba671 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -84,24 +84,9 @@ def concat_files(file_list, reader=None, intersperse=""): files_concat = intersperse.join(files_content) return files_concat,files_timestamp -html_template = """ - - - - OpenERP - - %(css)s - %(js)s - - - - -""" +html_template = None +with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "main.html")) as html_file: + html_template = html_file.read() class WebClient(openerpweb.Controller): _cp_path = "/web/webclient" @@ -207,7 +192,6 @@ class WebClient(openerpweb.Controller): 'js': js, 'css': css, 'modules': simplejson.dumps(self.server_wide_modules(req)), - 'init': 'new s.web.WebClient().start();', } return r @@ -229,7 +213,7 @@ class WebClient(openerpweb.Controller): else: lang_obj = None - if lang.count("_") > 0: + if "_" in lang: separator = "_" else: separator = "@" @@ -240,15 +224,15 @@ class WebClient(openerpweb.Controller): for addon_name in mods: transl = {"messages":[]} transs[addon_name] = transl + addons_path = openerpweb.addons_manifest[addon_name]['addons_path'] for l in langs: - addons_path = openerpweb.addons_manifest[addon_name]['addons_path'] f_name = os.path.join(addons_path, addon_name, "po", l + ".po") if not os.path.exists(f_name): continue try: with open(f_name) as t_file: po = babel.messages.pofile.read_po(t_file) - except: + except Exception: continue for x in po: if x.id and x.string: @@ -337,18 +321,13 @@ class Database(openerpweb.Controller): @openerpweb.httprequest def backup(self, req, backup_db, backup_pwd, token): - try: - db_dump = base64.b64decode( - req.session.proxy("db").dump(backup_pwd, backup_db)) - return req.make_response(db_dump, - [('Content-Type', 'application/octet-stream; charset=binary'), - ('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')], - {'fileToken': int(token)} - ) - except xmlrpclib.Fault, e: - if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': - return 'Backup Database|' + e.faultCode - return 'Backup Database|Could not generate database backup' + db_dump = base64.b64decode( + req.session.proxy("db").dump(backup_pwd, backup_db)) + return req.make_response(db_dump, + [('Content-Type', 'application/octet-stream; charset=binary'), + ('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')], + {'fileToken': int(token)} + ) @openerpweb.httprequest def restore(self, req, db_file, restore_pwd, new_db): @@ -376,6 +355,7 @@ class Session(openerpweb.Controller): _cp_path = "/web/session" def session_info(self, req): + req.session.ensure_valid() return { "session_id": req.session_id, "uid": req.session._uid, @@ -415,7 +395,7 @@ class Session(openerpweb.Controller): if req.session.model('res.users').change_password( old_password, new_password): return {'new_password':new_password} - except: + except Exception: return {'error': 'Original password incorrect, your password was not changed.', 'title': 'Change Password'} return {'error': 'Error, password not changed !', 'title': 'Change Password'} @@ -524,7 +504,7 @@ class Session(openerpweb.Controller): req.httpsession['saved_actions'] = saved_actions # we don't allow more than 10 stored actions if len(saved_actions["actions"]) >= 10: - del saved_actions["actions"][min(saved_actions["actions"].keys())] + del saved_actions["actions"][min(saved_actions["actions"])] key = saved_actions["next"] saved_actions["actions"][key] = the_action saved_actions["next"] = key + 1 @@ -585,10 +565,8 @@ def clean_action(req, action, do_not_eval=False): if 'domain' in action: action['domain'] = parse_domain(action['domain'], req.session) - if 'type' not in action: - action['type'] = 'ir.actions.act_window_close' - - if action['type'] == 'ir.actions.act_window': + action_type = action.setdefault('type', 'ir.actions.act_window_close') + if action_type == 'ir.actions.act_window': return fix_view_modes(action) return action @@ -707,7 +685,7 @@ class Menu(openerpweb.Controller): # sort by sequence a tree using parent_id for menu_item in menu_items: menu_item.setdefault('children', []).sort( - key=lambda x:x["sequence"]) + key=operator.itemgetter('sequence')) return menu_root @@ -760,7 +738,7 @@ class DataSet(openerpweb.Controller): # shortcut read if we only want the ids return { 'ids': ids, - 'records': map(lambda id: {'id': id}, paginated_ids) + 'records': [{'id': id} for id in paginated_ids] } records = Model.read(paginated_ids, fields or False, context) @@ -859,6 +837,36 @@ class DataSet(openerpweb.Controller): return getattr(req.session.model(model), method)(*args, **kwargs) + @openerpweb.jsonrequest + def onchange(self, req, model, method, args, context_id=None): + """ Support method for handling onchange calls: behaves much like call + with the following differences: + + * Does not take a domain_id + * Is aware of the return value's structure, and will parse the domains + if needed in order to return either parsed literal domains (in JSON) + or non-literal domain instances, allowing those domains to be used + from JS + + :param req: + :type req: web.common.http.JsonRequest + :param str model: object type on which to call the method + :param str method: name of the onchange handler method + :param list args: arguments to call the onchange handler with + :param int context_id: index of the context object in the list of + arguments + :return: result of the onchange call with all domains parsed + """ + result = self.call_common(req, model, method, args, context_id=context_id) + if 'domain' not in result: + return result + + result['domain'] = dict( + (k, parse_domain(v, req.session)) + for k, v in result['domain'].iteritems()) + + return result + @openerpweb.jsonrequest def call(self, req, model, method, args, domain_id=None, context_id=None): return self.call_common(req, model, method, args, domain_id, context_id) @@ -1031,7 +1039,7 @@ def parse_domain(domain, session): :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ - if not isinstance(domain, (str, unicode)): + if not isinstance(domain, basestring): return domain try: return ast.literal_eval(domain) @@ -1048,7 +1056,7 @@ def parse_context(context, session): :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ - if not isinstance(context, (str, unicode)): + if not isinstance(context, basestring): return context try: return ast.literal_eval(context) @@ -1516,7 +1524,7 @@ class CSVExport(Export): d = d.replace('\n',' ').replace('\t',' ') try: d = d.encode('utf-8') - except: + except UnicodeError: pass if d is False: d = None row.append(d) diff --git a/addons/web/po/zh_CN.po b/addons/web/po/zh_CN.po index 087c66bd3b3..299ff9e0a14 100644 --- a/addons/web/po/zh_CN.po +++ b/addons/web/po/zh_CN.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openerp-web\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-12-20 18:48+0100\n" -"PO-Revision-Date: 2012-01-12 05:44+0000\n" -"Last-Translator: Wei \"oldrev\" Li \n" +"PO-Revision-Date: 2012-01-19 13:35+0000\n" +"Last-Translator: Jeff Wang \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-01-13 05:01+0000\n" -"X-Generator: Launchpad (build 14664)\n" +"X-Launchpad-Export-Date: 2012-01-20 05:05+0000\n" +"X-Generator: Launchpad (build 14700)\n" #: addons/web/static/src/js/chrome.js:162 #: addons/web/static/src/js/chrome.js:175 @@ -107,12 +107,12 @@ msgstr "无效的搜索" #: addons/web/static/src/js/search.js:403 msgid "triggered from search view" -msgstr "" +msgstr "在搜索视图进入" #: addons/web/static/src/js/search.js:490 #, python-format msgid "Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s" -msgstr "" +msgstr "字段值有误。%(fieldname)s: [%(value)s] : %(message)s" #: addons/web/static/src/js/search.js:822 msgid "not a valid integer" @@ -205,7 +205,7 @@ msgstr "为假" #: addons/web/static/src/js/view_editor.js:42 msgid "ViewEditor" -msgstr "" +msgstr "界面设计器" #: addons/web/static/src/js/view_editor.js:46 #: addons/web/static/src/js/view_list.js:17 @@ -414,7 +414,7 @@ msgstr "#{text}" #: addons/web/static/src/xml/base.xml:0 msgid "Powered by" -msgstr "" +msgstr "自豪地使用" #: addons/web/static/src/xml/base.xml:0 msgid "openerp.com" @@ -617,7 +617,7 @@ msgstr "调试视图#" #: addons/web/static/src/xml/base.xml:0 msgid "- Fields View Get" -msgstr "" +msgstr "界面源代码" #: addons/web/static/src/xml/base.xml:0 msgid "- Edit" @@ -673,7 +673,7 @@ msgstr "“" #: addons/web/static/src/xml/base.xml:0 msgid "Modifiers:" -msgstr "" +msgstr "属性" #: addons/web/static/src/xml/base.xml:0 msgid "?" @@ -681,7 +681,7 @@ msgstr "?" #: addons/web/static/src/xml/base.xml:0 msgid "(nolabel)" -msgstr "" +msgstr "无标签" #: addons/web/static/src/xml/base.xml:0 msgid "Field:" @@ -713,7 +713,7 @@ msgstr "域:" #: addons/web/static/src/xml/base.xml:0 msgid "On change:" -msgstr "" +msgstr "变更时动作:" #: addons/web/static/src/xml/base.xml:0 msgid "Relation:" @@ -721,7 +721,7 @@ msgstr "关系:" #: addons/web/static/src/xml/base.xml:0 msgid "Selection:" -msgstr "" +msgstr "下拉列表:" #: addons/web/static/src/xml/base.xml:0 msgid "[" @@ -777,11 +777,11 @@ msgstr "按钮" #: addons/web/static/src/xml/base.xml:0 msgid "(no string)" -msgstr "" +msgstr "无字符串" #: addons/web/static/src/xml/base.xml:0 msgid "Special:" -msgstr "" +msgstr "特殊:" #: addons/web/static/src/xml/base.xml:0 msgid "Button Type:" @@ -875,7 +875,7 @@ msgstr "导出类型:" #: addons/web/static/src/xml/base.xml:0 msgid "Import Compatible Export" -msgstr "" +msgstr "用可导入的格式导出" #: addons/web/static/src/xml/base.xml:0 msgid "Export all Data" @@ -938,6 +938,8 @@ msgid "" "Select a .CSV file to import. If you need a sample of file to import,\n" " you should use the export tool with the \"Import Compatible\" option." msgstr "" +"选择要导入的CSV文件。如果需要导入文件的模版,\n" +"可以用导出工具并选中 “导入兼容”" #: addons/web/static/src/xml/base.xml:0 msgid "CSV File:" @@ -1032,6 +1034,10 @@ msgid "" "supply chain,\n" " project management, production, services, CRM, etc..." msgstr "" +"是个自由的企业级软件系统,通过信息集成提升企业\n" +"生产力和盈利能力。它连接、改进和管控企业流程的\n" +"方方面面。包括销售、财务、供应链、项目管理、生\n" +"产、服务和客户关系管理等" #: addons/web/static/src/xml/base.xml:0 msgid "" @@ -1044,6 +1050,9 @@ msgid "" " production system and migration to a new version to be " "straightforward." msgstr "" +"该系统是跨平台的,可以安装在Windows、Mac OSX\n" +"和各种Linux或类UNIX发行版上。 它的架构支持\n" +"快速开发新功能、修改现有功能或直接升级到新版本" #: addons/web/static/src/xml/base.xml:0 msgid "" diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 32c7f14b345..13068e123a5 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -171,6 +171,7 @@ body { padding: 0; margin: 0; } padding: 5px 10px; color: #eee; border: solid 1px #333; + background: #1e1e1e; background: rgba(30,30,30,0.94); -moz-border-radius: 0 0 8px 8px; -webkit-border-radius: 0 0 8px 8px; @@ -197,6 +198,7 @@ body { padding: 0; margin: 0; } left: 50%; margin: -160px -166px; border: solid 1px #333333; + background: #1e1e1e; background: rgba(30,30,30,0.94); padding: 22px 32px; text-align: left; diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 8b9a0d4efc6..06fcf518002 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -472,16 +472,13 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database self.blockUI(); self.session.get_file({ form: form, - error: function (body) { - var error = body.firstChild.data.split('|'); - self.display_error({ - title: error[0], - error: error[1] - }); + success: function () { + self.do_notify(_t("Backed"), + _t("Database backed up successfully")); }, + error: openerp.webclient.crashmanager.on_rpc_error, complete: function() { self.unblockUI(); - self.do_notify(_t("Backed"), _t("Database backed up successfully")); } }); } @@ -1064,7 +1061,6 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie }, start: function() { var self = this; - this.$element = $(document.body); if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 3005fc80d13..43bd8a0abf7 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -809,8 +809,15 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. .attr({id: id, name: id}) .appendTo(document.body) .load(function () { - if (options.error) { options.error(this.contentDocument.body); } - complete(); + try { + if (options.error) { + options.error(JSON.parse( + this.contentDocument.body.childNodes[1].textContent + )); + } + } finally { + complete(); + } }); if (options.form) { diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index ebcbfc18100..2172ae103b8 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -316,7 +316,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var change_spec = self.parse_on_change(on_change, widget); if (change_spec) { var ajax = { - url: '/web/dataset/call', + url: '/web/dataset/onchange', async: false }; return self.rpc(ajax, { @@ -342,6 +342,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var result = response; if (result.value) { for (var f in result.value) { + if (!result.value.hasOwnProperty(f)) { continue; } var field = this.fields[f]; // If field is not defined in the view, just ignore it if (field) { @@ -367,7 +368,14 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); } if (result.domain) { - // TODO: + function edit_domain(node) { + var new_domain = result.domain[node.attrs.name]; + if (new_domain) { + node.attrs.domain = new_domain; + } + _(node.children).each(edit_domain); + } + edit_domain(this.fields_view.arch); } return $.Deferred().resolve(); } catch(e) { @@ -537,6 +545,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# get_fields_values: function(blacklist) { blacklist = blacklist || []; var values = {}; + var ids = this.get_selected_ids(); + values["id"] = ids.length > 0 ? ids[0] : false; _.each(this.fields, function(value, key) { if (_.include(blacklist, key)) return; @@ -790,7 +800,7 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. return QWeb.render(template, { "widget": this }); }, do_attach_tooltip: function(widget, trigger, options) { - if ($.browser.mozilla && parseInt($.browser.version.split('.')[0], 10) < 2) { + if ($.browser.mozilla) { // Unknown bug in old version of firefox : // input type=text onchange event not fired when tootip is shown return; diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index fe4579c8c25..5d8c7498376 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -195,7 +195,8 @@ session.web.ActionManager = session.web.Widget.extend({ on_closed(); } self.dialog_stop(); - } + }, + error: session.webclient.crashmanager.on_rpc_error }) }); }, @@ -310,11 +311,15 @@ session.web.ViewManager = session.web.Widget.extend(/** @lends session.web.View this.views[view_type].deferred.resolve(view_type); $.when(view_promise).then(function() { self.on_controller_inited(view_type, controller); - if (self.searchview && view.controller.searchable !== false) { + if (self.searchview + && self.flags.auto_search !== false + && view.controller.searchable !== false) { self.searchview.ready.then(self.searchview.do_search); } }); - } else if (this.searchview && view.controller.searchable !== false) { + } else if (this.searchview + && self.flags.auto_search !== false + && view.controller.searchable !== false) { this.searchview.ready.then(this.searchview.do_search); } @@ -448,6 +453,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner // do not have it yet (and we don't, because we've not called our own // ``_super()``) rpc requests will blow up. var flags = action.flags || {}; + flags.auto_search = !!action.auto_search; if (action.res_model == 'board.board' && action.view_mode === 'form') { // Special case for Dashboards _.extend(flags, { diff --git a/addons/web_dashboard/po/zh_CN.po b/addons/web_dashboard/po/zh_CN.po index c8701dd375e..f201f6994b2 100644 --- a/addons/web_dashboard/po/zh_CN.po +++ b/addons/web_dashboard/po/zh_CN.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openerp-web\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-12-20 18:48+0100\n" -"PO-Revision-Date: 2012-01-12 05:35+0000\n" -"Last-Translator: Wei \"oldrev\" Li \n" +"PO-Revision-Date: 2012-01-19 13:40+0000\n" +"Last-Translator: Jeff Wang \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-01-13 05:01+0000\n" -"X-Generator: Launchpad (build 14664)\n" +"X-Launchpad-Export-Date: 2012-01-20 05:05+0000\n" +"X-Generator: Launchpad (build 14700)\n" #: addons/web_dashboard/static/src/js/dashboard.js:63 msgid "Edit Layout" @@ -53,7 +53,7 @@ msgstr "%" msgid "" "Click on the functionalites listed below to launch them and configure your " "system" -msgstr "" +msgstr "单击以下功能列表打开配置界面" #: addons/web_dashboard/static/src/xml/web_dashboard.xml:0 msgid "Welcome to OpenERP" diff --git a/addons/web_diagram/po/zh_CN.po b/addons/web_diagram/po/zh_CN.po index 63d570efe65..81df135c4c0 100644 --- a/addons/web_diagram/po/zh_CN.po +++ b/addons/web_diagram/po/zh_CN.po @@ -8,18 +8,18 @@ msgstr "" "Project-Id-Version: openerp-web\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-12-20 18:48+0100\n" -"PO-Revision-Date: 2012-01-12 05:36+0000\n" -"Last-Translator: Wei \"oldrev\" Li \n" +"PO-Revision-Date: 2012-01-19 13:42+0000\n" +"Last-Translator: Jeff Wang \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-01-13 05:01+0000\n" -"X-Generator: Launchpad (build 14664)\n" +"X-Launchpad-Export-Date: 2012-01-20 05:05+0000\n" +"X-Generator: Launchpad (build 14700)\n" #: addons/web_diagram/static/src/js/diagram.js:11 msgid "Diagram" -msgstr "" +msgstr "图表" #: addons/web_diagram/static/src/js/diagram.js:210 msgid "Cancel" diff --git a/addons/web_graph/po/zh_CN.po b/addons/web_graph/po/zh_CN.po index 6ff1d55c867..6f3e8e5f8ff 100644 --- a/addons/web_graph/po/zh_CN.po +++ b/addons/web_graph/po/zh_CN.po @@ -8,15 +8,15 @@ msgstr "" "Project-Id-Version: openerp-web\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-12-20 18:48+0100\n" -"PO-Revision-Date: 2012-01-07 05:43+0000\n" -"Last-Translator: FULL NAME \n" +"PO-Revision-Date: 2012-01-19 13:43+0000\n" +"Last-Translator: hifly \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-01-08 05:27+0000\n" -"X-Generator: Launchpad (build 14640)\n" +"X-Launchpad-Export-Date: 2012-01-20 05:05+0000\n" +"X-Generator: Launchpad (build 14700)\n" #: addons/web_graph/static/src/js/graph.js:19 msgid "Graph" -msgstr "" +msgstr "图形"