From 9ae41c29a2ec4b8eea3876c916859e3174e84a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=BB=20=D0=92=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B1=D1=8C=D0=B5=D0=B2?= Date: Fri, 3 May 2024 12:03:07 +0000 Subject: [PATCH] initial commit --- __init__.py | 4 + __manifest__.py | 40 + __pycache__/__init__.cpython-311.pyc | Bin 0 -> 304 bytes __pycache__/websocket.cpython-311.pyc | Bin 0 -> 52309 bytes controllers/__init__.py | 5 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 309 bytes controllers/__pycache__/home.cpython-311.pyc | Bin 0 -> 2635 bytes controllers/__pycache__/main.cpython-311.pyc | Bin 0 -> 1205 bytes .../__pycache__/websocket.cpython-311.pyc | Bin 0 -> 5422 bytes controllers/home.py | 33 + controllers/main.py | 13 + controllers/websocket.py | 64 ++ i18n/af.po | 150 +++ i18n/am.po | 146 +++ i18n/ar.po | 157 +++ i18n/az.po | 151 +++ i18n/bg.po | 157 +++ i18n/bs.po | 151 +++ i18n/bus.pot | 150 +++ i18n/ca.po | 165 +++ i18n/cs.po | 160 +++ i18n/da.po | 157 +++ i18n/de.po | 158 +++ i18n/el.po | 151 +++ i18n/en_AU.po | 148 +++ i18n/en_GB.po | 149 +++ i18n/es.po | 158 +++ i18n/es_419.po | 158 +++ i18n/es_BO.po | 149 +++ i18n/es_CL.po | 149 +++ i18n/es_CO.po | 149 +++ i18n/es_CR.po | 149 +++ i18n/es_DO.po | 149 +++ i18n/es_EC.po | 149 +++ i18n/es_PA.po | 148 +++ i18n/es_PE.po | 149 +++ i18n/es_PY.po | 149 +++ i18n/es_VE.po | 149 +++ i18n/et.po | 162 +++ i18n/eu.po | 149 +++ i18n/fa.po | 164 +++ i18n/fi.po | 163 +++ i18n/fo.po | 149 +++ i18n/fr.po | 157 +++ i18n/fr_BE.po | 148 +++ i18n/fr_CA.po | 149 +++ i18n/gl.po | 149 +++ i18n/gu.po | 150 +++ i18n/he.po | 161 +++ i18n/hr.po | 153 +++ i18n/hu.po | 159 +++ i18n/hy.po | 150 +++ i18n/id.po | 157 +++ i18n/is.po | 150 +++ i18n/it.po | 157 +++ i18n/ja.po | 156 +++ i18n/ka.po | 149 +++ i18n/kab.po | 149 +++ i18n/km.po | 150 +++ i18n/ko.po | 156 +++ i18n/lb.po | 146 +++ i18n/lo.po | 146 +++ i18n/lt.po | 161 +++ i18n/lv.po | 159 +++ i18n/mk.po | 149 +++ i18n/ml_IN.po | 148 +++ i18n/mn.po | 152 +++ i18n/nb.po | 151 +++ i18n/ne.po | 146 +++ i18n/nl.po | 157 +++ i18n/pl.po | 157 +++ i18n/pt.po | 154 +++ i18n/pt_BR.po | 158 +++ i18n/ro.po | 153 +++ i18n/ru.po | 160 +++ i18n/sk.po | 154 +++ i18n/sl.po | 157 +++ i18n/sq.po | 149 +++ i18n/sr.po | 160 +++ i18n/sr@latin.po | 152 +++ i18n/sv.po | 166 +++ i18n/ta.po | 148 +++ i18n/th.po | 158 +++ i18n/tr.po | 166 +++ i18n/uk.po | 158 +++ i18n/vi.po | 158 +++ i18n/zh_CN.po | 154 +++ i18n/zh_TW.po | 154 +++ models/__init__.py | 7 + models/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 457 bytes models/__pycache__/bus.cpython-311.pyc | Bin 0 -> 15713 bytes .../__pycache__/bus_presence.cpython-311.pyc | Bin 0 -> 5102 bytes models/__pycache__/ir_model.cpython-311.pyc | Bin 0 -> 2567 bytes .../__pycache__/ir_websocket.cpython-311.pyc | Bin 0 -> 5264 bytes .../__pycache__/res_partner.cpython-311.pyc | Bin 0 -> 2396 bytes models/__pycache__/res_users.cpython-311.pyc | Bin 0 -> 2203 bytes models/bus.py | 238 +++++ models/bus_presence.py | 76 ++ models/ir_model.py | 35 + models/ir_websocket.py | 63 ++ models/res_partner.py | 29 + models/res_users.py | 28 + security/ir.model.access.csv | 4 + static/src/bus_parameters_service.js | 13 + static/src/im_status_service.js | 77 ++ static/src/misc.js | 65 ++ static/src/multi_tab_service.js | 223 ++++ .../src/services/assets_watchdog_service.js | 67 ++ static/src/services/bus_service.js | 208 ++++ static/src/services/presence_service.js | 61 ++ static/src/simple_notification_service.js | 15 + static/src/workers/websocket_worker.js | 474 +++++++++ static/src/workers/websocket_worker_script.js | 22 + static/src/workers/websocket_worker_utils.js | 29 + static/tests/assets_watchdog_tests.js | 35 + static/tests/bus_tests.js | 393 ++++++++ .../tests/helpers/mock_python_environment.js | 342 +++++++ static/tests/helpers/mock_server.js | 172 ++++ .../mock_server/models/ir_websocket.js | 58 ++ static/tests/helpers/mock_services.js | 15 + static/tests/helpers/mock_websocket.js | 108 ++ .../helpers/model_definitions_helpers.js | 57 ++ .../tests/helpers/model_definitions_setup.js | 79 ++ static/tests/helpers/test_constants.js | 13 + static/tests/helpers/test_utils.js | 17 + .../tests/helpers/view_definitions_setup.js | 30 + .../tests/helpers/websocket_event_deferred.js | 226 +++++ static/tests/multi_tab_service_tests.js | 95 ++ static/tests/simple_notification_tests.js | 69 ++ static/tests/websocket_worker_tests.js | 94 ++ tests/__init__.py | 9 + tests/common.py | 139 +++ tests/test_assetsbundle.py | 44 + tests/test_health.py | 12 + tests/test_ir_model.py | 50 + tests/test_ir_websocket.py | 13 + tests/test_notify.py | 49 + tests/test_websocket_caryall.py | 258 +++++ tests/test_websocket_controller.py | 71 ++ tests/test_websocket_rate_limiting.py | 66 ++ websocket.py | 949 ++++++++++++++++++ 141 files changed, 17029 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-311.pyc create mode 100644 __pycache__/websocket.cpython-311.pyc create mode 100644 controllers/__init__.py create mode 100644 controllers/__pycache__/__init__.cpython-311.pyc create mode 100644 controllers/__pycache__/home.cpython-311.pyc create mode 100644 controllers/__pycache__/main.cpython-311.pyc create mode 100644 controllers/__pycache__/websocket.cpython-311.pyc create mode 100644 controllers/home.py create mode 100644 controllers/main.py create mode 100644 controllers/websocket.py create mode 100644 i18n/af.po create mode 100644 i18n/am.po create mode 100644 i18n/ar.po create mode 100644 i18n/az.po create mode 100644 i18n/bg.po create mode 100644 i18n/bs.po create mode 100644 i18n/bus.pot create mode 100644 i18n/ca.po create mode 100644 i18n/cs.po create mode 100644 i18n/da.po create mode 100644 i18n/de.po create mode 100644 i18n/el.po create mode 100644 i18n/en_AU.po create mode 100644 i18n/en_GB.po create mode 100644 i18n/es.po create mode 100644 i18n/es_419.po create mode 100644 i18n/es_BO.po create mode 100644 i18n/es_CL.po create mode 100644 i18n/es_CO.po create mode 100644 i18n/es_CR.po create mode 100644 i18n/es_DO.po create mode 100644 i18n/es_EC.po create mode 100644 i18n/es_PA.po create mode 100644 i18n/es_PE.po create mode 100644 i18n/es_PY.po create mode 100644 i18n/es_VE.po create mode 100644 i18n/et.po create mode 100644 i18n/eu.po create mode 100644 i18n/fa.po create mode 100644 i18n/fi.po create mode 100644 i18n/fo.po create mode 100644 i18n/fr.po create mode 100644 i18n/fr_BE.po create mode 100644 i18n/fr_CA.po create mode 100644 i18n/gl.po create mode 100644 i18n/gu.po create mode 100644 i18n/he.po create mode 100644 i18n/hr.po create mode 100644 i18n/hu.po create mode 100644 i18n/hy.po create mode 100644 i18n/id.po create mode 100644 i18n/is.po create mode 100644 i18n/it.po create mode 100644 i18n/ja.po create mode 100644 i18n/ka.po create mode 100644 i18n/kab.po create mode 100644 i18n/km.po create mode 100644 i18n/ko.po create mode 100644 i18n/lb.po create mode 100644 i18n/lo.po create mode 100644 i18n/lt.po create mode 100644 i18n/lv.po create mode 100644 i18n/mk.po create mode 100644 i18n/ml_IN.po create mode 100644 i18n/mn.po create mode 100644 i18n/nb.po create mode 100644 i18n/ne.po create mode 100644 i18n/nl.po create mode 100644 i18n/pl.po create mode 100644 i18n/pt.po create mode 100644 i18n/pt_BR.po create mode 100644 i18n/ro.po create mode 100644 i18n/ru.po create mode 100644 i18n/sk.po create mode 100644 i18n/sl.po create mode 100644 i18n/sq.po create mode 100644 i18n/sr.po create mode 100644 i18n/sr@latin.po create mode 100644 i18n/sv.po create mode 100644 i18n/ta.po create mode 100644 i18n/th.po create mode 100644 i18n/tr.po create mode 100644 i18n/uk.po create mode 100644 i18n/vi.po create mode 100644 i18n/zh_CN.po create mode 100644 i18n/zh_TW.po create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-311.pyc create mode 100644 models/__pycache__/bus.cpython-311.pyc create mode 100644 models/__pycache__/bus_presence.cpython-311.pyc create mode 100644 models/__pycache__/ir_model.cpython-311.pyc create mode 100644 models/__pycache__/ir_websocket.cpython-311.pyc create mode 100644 models/__pycache__/res_partner.cpython-311.pyc create mode 100644 models/__pycache__/res_users.cpython-311.pyc create mode 100644 models/bus.py create mode 100644 models/bus_presence.py create mode 100644 models/ir_model.py create mode 100644 models/ir_websocket.py create mode 100644 models/res_partner.py create mode 100644 models/res_users.py create mode 100644 security/ir.model.access.csv create mode 100644 static/src/bus_parameters_service.js create mode 100644 static/src/im_status_service.js create mode 100644 static/src/misc.js create mode 100644 static/src/multi_tab_service.js create mode 100644 static/src/services/assets_watchdog_service.js create mode 100644 static/src/services/bus_service.js create mode 100644 static/src/services/presence_service.js create mode 100644 static/src/simple_notification_service.js create mode 100644 static/src/workers/websocket_worker.js create mode 100644 static/src/workers/websocket_worker_script.js create mode 100644 static/src/workers/websocket_worker_utils.js create mode 100644 static/tests/assets_watchdog_tests.js create mode 100644 static/tests/bus_tests.js create mode 100644 static/tests/helpers/mock_python_environment.js create mode 100644 static/tests/helpers/mock_server.js create mode 100644 static/tests/helpers/mock_server/models/ir_websocket.js create mode 100644 static/tests/helpers/mock_services.js create mode 100644 static/tests/helpers/mock_websocket.js create mode 100644 static/tests/helpers/model_definitions_helpers.js create mode 100644 static/tests/helpers/model_definitions_setup.js create mode 100644 static/tests/helpers/test_constants.js create mode 100644 static/tests/helpers/test_utils.js create mode 100644 static/tests/helpers/view_definitions_setup.js create mode 100644 static/tests/helpers/websocket_event_deferred.js create mode 100644 static/tests/multi_tab_service_tests.js create mode 100644 static/tests/simple_notification_tests.js create mode 100644 static/tests/websocket_worker_tests.js create mode 100644 tests/__init__.py create mode 100644 tests/common.py create mode 100644 tests/test_assetsbundle.py create mode 100644 tests/test_health.py create mode 100644 tests/test_ir_model.py create mode 100644 tests/test_ir_websocket.py create mode 100644 tests/test_notify.py create mode 100644 tests/test_websocket_caryall.py create mode 100644 tests/test_websocket_controller.py create mode 100644 tests/test_websocket_rate_limiting.py create mode 100644 websocket.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..9ab5f17 --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import models +from . import controllers +from . import websocket diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..d29765f --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,40 @@ +{ + 'name' : 'IM Bus', + 'version': '1.0', + 'category': 'Hidden', + 'description': "Instant Messaging Bus allow you to send messages to users, in live.", + 'depends': ['base', 'web'], + 'data': [ + 'security/ir.model.access.csv', + ], + 'installable': True, + 'auto_install': True, + 'assets': { + 'web.assets_backend': [ + 'bus/static/src/*.js', + 'bus/static/src/services/**/*.js', + 'bus/static/src/workers/websocket_worker.js', + 'bus/static/src/workers/websocket_worker_utils.js', + ], + 'web.assets_frontend': [ + 'bus/static/src/*.js', + 'bus/static/src/services/**/*.js', + ('remove', 'bus/static/src/services/assets_watchdog_service.js'), + ('remove', 'bus/static/src/simple_notification_service.js'), + 'bus/static/src/workers/websocket_worker.js', + 'bus/static/src/workers/websocket_worker_utils.js', + ], + 'web.tests_assets': [ + 'bus/static/tests/helpers/**/*', + ], + 'web.qunit_suite_tests': [ + 'bus/static/tests/**/*.js', + ('remove', 'bus/static/tests/helpers/**/*'), + ], + 'bus.websocket_worker_assets': [ + 'web/static/src/module_loader.js', + 'bus/static/src/workers/*', + ], + }, + 'license': 'LGPL-3', +} diff --git a/__pycache__/__init__.cpython-311.pyc b/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6759313ed3c807cf17861c6dab40216547e834d8 GIT binary patch literal 304 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 MVUWK7L*Pai0F?WEJ^%m! literal 0 HcmV?d00001 diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000..33497e4 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import home +from . import main +from . import websocket diff --git a/controllers/__pycache__/__init__.cpython-311.pyc b/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..862a994274cb347c361daf8552d43dbf1da2e3f2 GIT binary patch literal 309 zcmZ3^%ge<80(*7R!hzIdFb7I7LFvz0K*n^26owSW9EM!RC`LvQn+eEfiedt@nSpHP zC}tp=C73~z^(7-vgC^rGmW=$|R1lq;n3)HpIm=U%iu04RQ%n3bS#B{DF$0wqv499x zAhD9+GmvEX<)9DJrk|3Ul$n^PpP!PS4<_Ob&GiiQ4D}OJQu6bP^^;1A^^^1SN{aGx za#D+m_2c6+^D;}~e*GI!t>2 literal 0 HcmV?d00001 diff --git a/controllers/__pycache__/home.cpython-311.pyc b/controllers/__pycache__/home.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaeef25327a62ec6ea954cf9462588da7f1002a7 GIT binary patch literal 2635 zcmb7F&2JM&6rcUD*I$WE;v!n0+5m|#&`opTs{~pppn%#2L_oB@tTvuWvSIBtGiyR3 zr^+ExX@%4rT9rd9`B0@)aOQ%vh*LS4OD0cE-@b9>=I!Y# zH_dC8a}w7mxmO{~16IB27D++sEEZi1{=(XhAq)RJG1h4dT|F$l9S(xucZ1!jjL|gm z!t=rWLf5MvD-e-(Tj=U;faRxM>kH9Pqhqr>12(mA&dY=xf>=FMH^ASg@Nc&jsVddt zp|rQhk>1@_N#f4<3eP8PAzk66zm2h-dto4PudwZg}d-@9kXZvgW&6wtH8Yj%>mc53Oa+8(AV;}q`mhIWT=|7$iB zoc=-S%{q~zkad|U9xGdnExHu*h=q%G=@z7*T4988nN^ktW-LaGvPHcTp=O}4oUy4< zbaBNYoi3+v6_cX-O1gL}CsA=R%W+DWA+RhlDo`Pa|g3e`iuberkkJkc?kwJMIM9}m394e8frVH_se67xt=sF4R{ zml3Rcu3jm5v;yN;51i0#Pbk2qF7>RECsYdaV!L3bSR~lCJmM_rvo6&cDO9NKE$Nh4 z%q=lPzoswRj-%ftq^y^$xjDNur!Tn`-I{SLVA&fIG4Phkglk2@m^DXulzDdH?ow_a z6;;Ed5JaoUc-$^q7(;XzPuOJ$X`4^ZdtSLy<~&IunjQ%zL5rDXYTpOm^TY9#DFI|p*QD<$73cVCG5ggibBstxKgn(eFu0d zDv!tJEoO?F4DOH#zje`GjgQU)?2KYEV_T(B7rSmi%(1h^`^MNP90H;=3Ku&nwxh1& zz{Lk^jPerCwiCw;Z`540Xo(Jkb+Icu0cf#`wzdAXFV+`)ZK!r! z#@7!1IO!i6^$%TWXyd;4CR<2~q}vLKeUDE5IR4~vJu%iujQNSN)rn>*{mtapllAQJ zMk?p0a;sOGT4H_pTf-kXR@cPWUYZ_UpQs-=;b%{7WzTJ9&(%Npw0?f7k-hF`uQ$>+ z{Pd00t4%GvXS3wMqYIBG>Pfwk)cvGhJLo6hTfN*=wbhArbt|K9X7onph@Ux9SC2N- zqrQ5ycFk9fE%ofCdiJ+S{li<$%z^E}Yzv|NA1f~q%6_7}3}|y;e@j#ENYNG&^aWBQ z+Dkz|-b;2MWT+rW*h0|W(P89EcmfE@OBm&4tUN@s5|-EEdEg#Mc@;`tr2?#{3gIeY zMP!tacYXr$o%C;yNUaOjpw?~pHRz;iuM7DIfUg&fB;@S@`Cjm=b)T26abN&C<=&?P zP9ab6KF5VJG+CukfMUu;gEFI8Siu!Y_8`A_gjLD_WofgmOrgvvDR?=RtBg3a^oUT8 zg~L2< zNds~xKC+$CYlDr{u%8;Pr-rxF*{>EKEpDYpHq#?D_T*|KecDf-uE$Q(5wMe!rl2xo zrdhIz#58%rG}~wke9AQMRV=4H6F9~DIxZK84?O-GNzrhO literal 0 HcmV?d00001 diff --git a/controllers/__pycache__/main.cpython-311.pyc b/controllers/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8164db96af313899a385eb016cbf8d8c6fe769f5 GIT binary patch literal 1205 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 PJN$%={ last_known_notification_id: + last = 0 + notifications = request.env['bus.bus']._poll(channels, last) + return {'channels': channels, 'notifications': notifications} + + @route('/websocket/update_bus_presence', type='json', auth='public', cors='*') + def update_bus_presence(self, inactivity_period, im_status_ids_by_model): + if 'is_websocket_session' not in request.session: + raise SessionExpiredException() + request.env['ir.websocket']._update_bus_presence(int(inactivity_period), im_status_ids_by_model) + return {} + + @route('/bus/websocket_worker_bundle', type='http', auth='public', cors='*') + def get_websocket_worker_bundle(self, v=None): # pylint: disable=unused-argument + """ + :param str v: Version of the worker, frontend only argument used to + prevent new worker versions to be loaded from the browser cache. + """ + bundle_name = 'bus.websocket_worker_assets' + bundle = request.env["ir.qweb"]._get_asset_bundle(bundle_name, debug_assets="assets" in request.session.debug) + stream = request.env['ir.binary']._get_stream_from(bundle.js()) + return stream.get_response() diff --git a/i18n/af.po b/i18n/af.po new file mode 100644 index 0000000..084dc17 --- /dev/null +++ b/i18n/af.po @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux, 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Martin Trigaux, 2022\n" +"Language-Team: Afrikaans (https://app.transifex.com/odoo/teams/41243/af/)\n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontak" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Geskep deur" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Geskep op" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Vertoningsnaam" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Laas Opgedateer deur" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Laas Opgedateer op" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Boodskap" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Gebruiker" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Gebruikers" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/am.po b/i18n/am.po new file mode 100644 index 0000000..a680c8c --- /dev/null +++ b/i18n/am.po @@ -0,0 +1,146 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Language-Team: Amharic (https://app.transifex.com/odoo/teams/41243/am/)\n" +"Language: am\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ar.po b/i18n/ar.po new file mode 100644 index 0000000..f21d038 --- /dev/null +++ b/i18n/ar.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Arabic (https://app.transifex.com/odoo/teams/41243/ar/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ar\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "بعيد" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "القناة" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "ناقل الاتصالات" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "جهة الاتصال" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "أنشئ بواسطة" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "أنشئ في" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "اسم العرض " + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "المُعرف" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "حالة المحادثات الفورية" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "آخر استطلاع" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "آخر حضور" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "آخر تحديث بواسطة" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "آخر تحديث في" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "الرسالة" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "النماذج" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "غير متصل بالإنترنت " + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "عبر الإنترنت " + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "تحديث " + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "يبدو أن هذه الصفحة قديمة. " + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "المستخدم" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "وجود المستخدم" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "المستخدمون" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"كلمة المرور الخاصة بك هي كلمة المرور الافتراضية (admin)! إذا كان هذا النظام " +"معرضااً للمستخدمين غير الموثوقين، من المهم تغييرها فورا لأسباب أمنية. سأظل " +"ألح عليك حول هذا الموضوع! " + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "التعامل مع رسائل websocket " diff --git a/i18n/az.po b/i18n/az.po new file mode 100644 index 0000000..13cca66 --- /dev/null +++ b/i18n/az.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# erpgo translator , 2022 +# Jumshud Sultanov , 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Jumshud Sultanov , 2022\n" +"Language-Team: Azerbaijani (https://app.transifex.com/odoo/teams/41243/az/)\n" +"Language: az\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Uzaq" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kommunikasiya Şini" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Tərəfindən yaradılıb" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Tarixdə yaradıldı" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Ekran Adı" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Son Sorğu- sual" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Son İştirak" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Son Yeniləyən" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Son Yenilənmə tarixi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mesaj" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modellər" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Oflayn" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Onlayn" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "İstifadəçi" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "İstifadəçi İştirakı" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "İstifadəçilər" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/bg.po b/i18n/bg.po new file mode 100644 index 0000000..8ff14ce --- /dev/null +++ b/i18n/bg.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# KeyVillage, 2023 +# Maria Boyadjieva , 2023 +# aleksandar ivanov, 2023 +# Albena Mincheva , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Albena Mincheva , 2023\n" +"Language-Team: Bulgarian (https://app.transifex.com/odoo/teams/41243/bg/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: bg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Извън" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Канал" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Контакт" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Създадено от" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Създадено на" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Име за Показване" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Статус IM " + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Последна анкета" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Последно присъствие" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Последно актуализирано от" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Последно актуализирано на" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Съобщение" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Модели" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Офлайн" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Онлайн" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Потребител" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Потребителско присъствие" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Потребители" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/bs.po b/i18n/bs.po new file mode 100644 index 0000000..e34b95a --- /dev/null +++ b/i18n/bs.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux, 2018 +# Boško Stojaković , 2018 +# Bole , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~11.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2018-09-21 13:17+0000\n" +"Last-Translator: Bole , 2018\n" +"Language-Team: Bosnian (https://www.transifex.com/odoo/teams/41243/bs/)\n" +"Language: bs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Kreirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Kreirano" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Prikazani naziv" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Zadnji ažurirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Zadnje ažurirano" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Poruka" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Van mreže" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Na mreži" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Korisnici" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/bus.pot b/i18n/bus.pot new file mode 100644 index 0000000..de0896f --- /dev/null +++ b/i18n/bus.pot @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 21:55+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ca.po b/i18n/ca.po new file mode 100644 index 0000000..a256dd9 --- /dev/null +++ b/i18n/ca.po @@ -0,0 +1,165 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Arnau Ros, 2023 +# Cristian Cruz, 2023 +# Sandra Franch , 2023 +# Óscar Fonseca , 2023 +# marcescu, 2023 +# Martin Trigaux, 2023 +# RGB Consulting , 2023 +# Jonatan Gk, 2023 +# Quim - eccit , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Quim - eccit , 2023\n" +"Language-Team: Catalan (https://app.transifex.com/odoo/teams/41243/ca/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Absent" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus de comunicació " + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contacte" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creat per" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Creat el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nom mostrat" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Estat de la conversa" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Última conversa" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Última presencia" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualització per" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualització el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Missatge" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Models" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Fora de línia" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "En línia" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Refresca" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Sembla que la pàgina està desactualitzada." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Usuari" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Presència del usuari" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Usuaris" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"La vostra contrasenya és per omissió (admin)! Si aquest sistema està exposat" +" als usuaris que no són de confiança és important canviar-lo immediatament " +"per motius de seguretat. Et continuaré molestant!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "gestió de missatges websocket" diff --git a/i18n/cs.po b/i18n/cs.po new file mode 100644 index 0000000..ce17eab --- /dev/null +++ b/i18n/cs.po @@ -0,0 +1,160 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Jakub Smolka, 2023 +# Ivana Bartonkova, 2023 +# Wil Odoo, 2023 +# karolína schusterová , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: karolína schusterová , 2023\n" +"Language-Team: Czech (https://app.transifex.com/odoo/teams/41243/cs/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: cs\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Pryč" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanál" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Komunikační sběrnice" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Vytvořeno uživatelem" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Vytvořeno dne" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Zobrazovací název" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM Status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Poslední průzkum" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Poslední přítomnost" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Naposledy upraveno uživatelem" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Naposledy upraveno dne" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Zpráva" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modely" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Nepřipojeno" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Obnovit" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Stránka se zdá být zastaralá." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Uživatel" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Přítomnost uživatele" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Uživatelé" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Vaše heslo je výchozí (admin)! Pokud je tento systém vystaven nedůvěryhodným" +" uživatelům, je důležité jej z bezpečnostních důvodů okamžitě změnit. Budu " +"vás o tom pořád otravovat!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "zpráva zpracování websocketu" diff --git a/i18n/da.po b/i18n/da.po new file mode 100644 index 0000000..dd10e9a --- /dev/null +++ b/i18n/da.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Martin Trigaux, 2023\n" +"Language-Team: Danish (https://app.transifex.com/odoo/teams/41243/da/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: da\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Væk" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kommunikations Bus" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Oprettet af" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Oprettet den" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Vis navn" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Seneste meningsmåling" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Sidste tilstedeværelse" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Sidst opdateret af" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Sidst opdateret den" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Besked" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeller" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Genopfrisk" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Siden ser ud til at være forældet." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Bruger" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Bruger tilstedeværelse" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Brugere" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Dit kodeord er sat til standard (admin)! Det er vigtigt, at du ændre det med" +" det samme, af sikkerhedsmæssige årsager, hvis dette system skulle udsættes " +"for tvivlsomme brugere. Jeg bliver ved med at irritere dig om det!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/de.po b/i18n/de.po new file mode 100644 index 0000000..9dbf0f9 --- /dev/null +++ b/i18n/de.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: German (https://app.transifex.com/odoo/teams/41243/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Abwesend" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kommunikationsbus" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Erstellt von" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Erstellt am" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Anzeigename" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Status der Sofortnachricht" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Letzte Befragung" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Letzte Anwesenheit" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Zuletzt aktualisiert von" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Zuletzt aktualisiert am" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Nachricht" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelle" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Aktualisieren" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Die Seite scheint veraltet zu sein." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Benutzer" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Benutzer-Anwesenheit" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Benutzer" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Ihr Passwort ist das Standardpasswort (admin)! Wenn dieses System für nicht " +"vertrauenswürdige Benutzer zugänglich ist, müssen Sie es aus " +"Sicherheitsgründen sofort ändern. Ich werde Sie immer wieder darauf " +"hinweisen!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "Websocket-Nachrichtbearbeitung" diff --git a/i18n/el.po b/i18n/el.po new file mode 100644 index 0000000..46ecdc0 --- /dev/null +++ b/i18n/el.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux, 2018 +# Kostas Goutoudis , 2018 +# George Tarasidis , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~11.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2018-09-21 13:17+0000\n" +"Last-Translator: George Tarasidis , 2018\n" +"Language-Team: Greek (https://www.transifex.com/odoo/teams/41243/el/)\n" +"Language: el\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Εκτός" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Κανάλι" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Επαφή" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Δημιουργήθηκε από" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Δημιουργήθηκε στις" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Εμφάνιση Ονόματος" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "Κωδικός" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Κατάσταση Άμεσης Συνομιλίας" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Τελευταία Δημοσκόπηση" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Τελευταία Παρουσία" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Τελευταία Ενημέρωση από" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Τελευταία Ενημέρωση στις" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Μήνυμα" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Παρουσία Χρήστη" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Χρήστες" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/en_AU.po b/i18n/en_AU.po new file mode 100644 index 0000000..4983f65 --- /dev/null +++ b/i18n/en_AU.po @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo 9.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2015-09-07 16:42+0000\n" +"Last-Translator: Martin Trigaux\n" +"Language-Team: English (Australia) (http://www.transifex.com/odoo/odoo-9/language/en_AU/)\n" +"Language: en_AU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Created by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Display Name" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Last Updated by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Last Updated on" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/en_GB.po b/i18n/en_GB.po new file mode 100644 index 0000000..49a3de6 --- /dev/null +++ b/i18n/en_GB.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: English (United Kingdom) (https://www.transifex.com/odoo/teams/41243/en_GB/)\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Created by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Display Name" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Last Updated by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Last Updated on" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es.po b/i18n/es.po new file mode 100644 index 0000000..5dbec74 --- /dev/null +++ b/i18n/es.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Larissa Manderfeld, 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Larissa Manderfeld, 2024\n" +"Language-Team: Spanish (https://app.transifex.com/odoo/teams/41243/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Ausente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus de comunicación" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Estado de mensajería instantanea" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Última encuesta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Última conexión" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mensaje" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Desconectado" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "En línea" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Actualizar" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Parece que la página no está actualizada." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Usuario" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Usuario conectado" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Usuarios" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"¡Tu contraseña es la predeterminada (admin)! Si este sistema está expuesto a" +" usuarios no confiables, es importante cambiarlo de inmediato por razones de" +" seguridad. ¡Te seguiré molestando al respecto!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "gestión de mensajes de WebSocket" diff --git a/i18n/es_419.po b/i18n/es_419.po new file mode 100644 index 0000000..1e02a10 --- /dev/null +++ b/i18n/es_419.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Fernanda Alvarez, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Fernanda Alvarez, 2023\n" +"Language-Team: Spanish (Latin America) (https://app.transifex.com/odoo/teams/41243/es_419/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es_419\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Ausente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus de comunicación" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre en pantalla" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Estado de mensajería instantanea" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Última encuesta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Última conexión" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mensaje" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Desconectado" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "En línea" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Actualizar" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Parece que la página no está actualizada." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Usuario" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Conexión del usuario" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Usuarios" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Está usando la contraseña predeterminada (admin). Si se expone este sistema " +"a usuarios no confiables, es importante que la cambie inmediatamente por " +"motivos de seguridad. Seguiré advirtiéndole al respecto." + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "gestión de mensajes de WebSocket" diff --git a/i18n/es_BO.po b/i18n/es_BO.po new file mode 100644 index 0000000..34554c8 --- /dev/null +++ b/i18n/es_BO.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Bolivia) (https://www.transifex.com/odoo/teams/41243/es_BO/)\n" +"Language: es_BO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_CL.po b/i18n/es_CL.po new file mode 100644 index 0000000..872890a --- /dev/null +++ b/i18n/es_CL.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Chile) (https://www.transifex.com/odoo/teams/41243/es_CL/)\n" +"Language: es_CL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_CO.po b/i18n/es_CO.po new file mode 100644 index 0000000..e715508 --- /dev/null +++ b/i18n/es_CO.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Colombia) (https://www.transifex.com/odoo/teams/41243/es_CO/)\n" +"Language: es_CO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre Público" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Actualizado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Actualizado" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_CR.po b/i18n/es_CR.po new file mode 100644 index 0000000..3c839dd --- /dev/null +++ b/i18n/es_CR.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Costa Rica) (https://www.transifex.com/odoo/teams/41243/es_CR/)\n" +"Language: es_CR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_DO.po b/i18n/es_DO.po new file mode 100644 index 0000000..aad6d68 --- /dev/null +++ b/i18n/es_DO.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Dominican Republic) (https://www.transifex.com/odoo/teams/41243/es_DO/)\n" +"Language: es_DO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_EC.po b/i18n/es_EC.po new file mode 100644 index 0000000..bf991ad --- /dev/null +++ b/i18n/es_EC.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Ecuador) (https://www.transifex.com/odoo/teams/41243/es_EC/)\n" +"Language: es_EC\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por:" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre a Mostrar" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Ultima Actualización por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Actualizado en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_PA.po b/i18n/es_PA.po new file mode 100644 index 0000000..7127dc1 --- /dev/null +++ b/i18n/es_PA.po @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo 9.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2015-09-07 16:42+0000\n" +"Last-Translator: Martin Trigaux\n" +"Language-Team: Spanish (Panama) (http://www.transifex.com/odoo/odoo-9/language/es_PA/)\n" +"Language: es_PA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_PE.po b/i18n/es_PE.po new file mode 100644 index 0000000..76a40d1 --- /dev/null +++ b/i18n/es_PE.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Peru) (https://www.transifex.com/odoo/teams/41243/es_PE/)\n" +"Language: es_PE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nombre a Mostrar" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Actualizado última vez por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Ultima Actualización" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_PY.po b/i18n/es_PY.po new file mode 100644 index 0000000..c35466d --- /dev/null +++ b/i18n/es_PY.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Paraguay) (https://www.transifex.com/odoo/teams/41243/es_PY/)\n" +"Language: es_PY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Ultima actualización por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Ultima actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/es_VE.po b/i18n/es_VE.po new file mode 100644 index 0000000..eaec0a9 --- /dev/null +++ b/i18n/es_VE.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Spanish (Venezuela) (https://www.transifex.com/odoo/teams/41243/es_VE/)\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Mostrar nombre" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización realizada por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Ultima actualizacion en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/et.po b/i18n/et.po new file mode 100644 index 0000000..332d496 --- /dev/null +++ b/i18n/et.po @@ -0,0 +1,162 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# JanaAvalah, 2023 +# Eneli Õigus , 2023 +# Marek Pontus, 2023 +# Maidu Targama , 2023 +# Arma Gedonsky , 2023 +# Triine Aavik , 2023 +# Anna, 2023 +# Leaanika Randmets, 2023 +# Martin Aavastik , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Martin Aavastik , 2023\n" +"Language-Team: Estonian (https://app.transifex.com/odoo/teams/41243/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Eemal" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Loodud (kelle poolt?)" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Loodud" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Kuvatav nimi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM Status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Last Poll" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Last Presence" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Viimati uuendatud" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Viimati uuendatud" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Sõnum" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Mudelid" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Võrguühenduseta" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Uuenda" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Leht on aegunud. " + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Kasutaja" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Kasutaja kohalolek" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Kasutajad" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket sõnumite käsitlus" diff --git a/i18n/eu.po b/i18n/eu.po new file mode 100644 index 0000000..04c218a --- /dev/null +++ b/i18n/eu.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Basque (https://www.transifex.com/odoo/teams/41243/eu/)\n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Nork sortua" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Izena erakutsi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Last Updated by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Last Updated on" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/fa.po b/i18n/fa.po new file mode 100644 index 0000000..434eb09 --- /dev/null +++ b/i18n/fa.po @@ -0,0 +1,164 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# F Hariri , 2023 +# Yousef Shadmanesh , 2023 +# Hanna Kheradroosta, 2023 +# Hamid Darabi, 2023 +# Mohsen Mohammadi , 2023 +# Hamed Mohammadi , 2023 +# Martin Trigaux, 2023 +# Mohammad Tahmasebi , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Mohammad Tahmasebi , 2023\n" +"Language-Team: Persian (https://app.transifex.com/odoo/teams/41243/fa/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fa\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "دور از کار" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "کانال" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "مخاطب" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "ایجاد شده توسط" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "ایجادشده در" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "نام نمایش داده شده" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "شناسه" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "وضعیت پیام رسانی" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "آخرین رای‌گیری" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "آخرین حضور" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "آخرین بروز رسانی توسط" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "آخرین بروز رسانی در" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "پیام" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "مدل ها" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "آفلاین" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "آنلاین" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "تازه سازی" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "صفحه به نظر می‌رسد قدیمی است." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "کاربر" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "حضور کاربر" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "کاربران" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"پسورد شما هنوز همان پسوورد دیفالت (admin) است! اگر این سیستم در دسترس افراد " +"غیر معتمد است، آن را به سرعت تغییر دهید. من در این مورد دوباره هشدار خواهم " +"داد." + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/fi.po b/i18n/fi.po new file mode 100644 index 0000000..41daa88 --- /dev/null +++ b/i18n/fi.po @@ -0,0 +1,163 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Veikko Väätäjä , 2023 +# Kari Lindgren , 2023 +# Martin Trigaux, 2023 +# Miku Laitinen , 2023 +# Tuomo Aura , 2023 +# Jarmo Kortetjärvi , 2023 +# Ossi Mantylahti , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Ossi Mantylahti , 2023\n" +"Language-Team: Finnish (https://app.transifex.com/odoo/teams/41243/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Poissa" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanava" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Viestintäväylä" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakti" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Luonut" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Luotu" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Näyttönimi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Pikaviestimen tila" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Viimeisin kysely" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Viimeksi läsnä" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Viimeksi päivittänyt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Viimeksi päivitetty" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Viesti" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Mallit" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Poissa" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Verkossa" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Päivitä" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Sivu näyttää vanhentuneelta." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Käyttäjä" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Käyttäjän läsnäolo" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Käyttäjät" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Salasanasi on oletussalasana (admin)! Jos tämä järjestelmä on alttiina " +"epäluotettaville käyttäjille, on tärkeää vaihtaa se välittömästi " +"turvallisuussyistä. Jatkan siitä nalkuttamista!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket-viestien käsittely" diff --git a/i18n/fo.po b/i18n/fo.po new file mode 100644 index 0000000..6672f80 --- /dev/null +++ b/i18n/fo.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Faroese (https://www.transifex.com/odoo/teams/41243/fo/)\n" +"Language: fo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Byrjað av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Vís navn" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Seinast dagført av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Seinast dagført tann" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po new file mode 100644 index 0000000..0889e6f --- /dev/null +++ b/i18n/fr.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: French (https://app.transifex.com/odoo/teams/41243/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Absent" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Chaîne" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus de communication" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contact" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nom d'affichage" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Statut de messagerie instantanée" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Dernier sondage" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Dernière présence en ligne" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Mis à jour par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Mis à jour le" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Message" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modèles" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Hors ligne" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "En ligne" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Actualiser" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "La page semble obsolète." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Utilisateur" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Présence de l'utilisateur" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Utilisateurs" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Votre mot de passe est celui par défaut (admin)! Si ce système est exposé à " +"des utilisateurs non fiables, il est important de le changer immédiatement " +"pour des raisons de sécurité. Je continuer à vous le rappeler !" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "traitement des messages du websocket " diff --git a/i18n/fr_BE.po b/i18n/fr_BE.po new file mode 100644 index 0000000..fe85000 --- /dev/null +++ b/i18n/fr_BE.po @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo 9.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2015-09-07 16:42+0000\n" +"Last-Translator: Martin Trigaux\n" +"Language-Team: French (Belgium) (http://www.transifex.com/odoo/odoo-9/language/fr_BE/)\n" +"Language: fr_BE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Derniere fois mis à jour par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Dernière mis à jour le" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Message" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Utilisateurs" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/fr_CA.po b/i18n/fr_CA.po new file mode 100644 index 0000000..2591131 --- /dev/null +++ b/i18n/fr_CA.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: French (Canada) (https://www.transifex.com/odoo/teams/41243/fr_CA/)\n" +"Language: fr_CA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "Identifiant" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/gl.po b/i18n/gl.po new file mode 100644 index 0000000..e6474f1 --- /dev/null +++ b/i18n/gl.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Galician (https://www.transifex.com/odoo/teams/41243/gl/)\n" +"Language: gl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/gu.po b/i18n/gu.po new file mode 100644 index 0000000..77d54e5 --- /dev/null +++ b/i18n/gu.po @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Qaidjohar Barbhaya, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Qaidjohar Barbhaya, 2023\n" +"Language-Team: Gujarati (https://app.transifex.com/odoo/teams/41243/gu/)\n" +"Language: gu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contact" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Created by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Created on" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Display Name" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Last Updated by" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Last Updated on" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "સંદેશ" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "User" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "વપરાશકર્તાઓ" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/he.po b/i18n/he.po new file mode 100644 index 0000000..1f281f7 --- /dev/null +++ b/i18n/he.po @@ -0,0 +1,161 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# MichaelHadar, 2023 +# NoaFarkash, 2023 +# Fishfur A Banter , 2023 +# ZVI BLONDER , 2023 +# Lilach Gilliam , 2023 +# Yihya Hugirat , 2023 +# Martin Trigaux, 2023 +# Ha Ketem , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Ha Ketem , 2023\n" +"Language-Team: Hebrew (https://app.transifex.com/odoo/teams/41243/he/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: he\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "רחוק" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "ערוץ" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "אפיק תקשורת" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "איש קשר" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "נוצר על-ידי" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "נוצר ב-" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "שם לתצוגה" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "מזהה" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "סטטוס IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "סקר אחרון" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "נוכחות אחרונה" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "עודכן לאחרונה על-ידי" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "עדכון אחרון ב" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "הודעה" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "מודלים" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "לא מקוון" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "מקוון" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "רענן" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "נראה שהדף הינו מיושן." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "משתמש" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "נוכחות משתמש" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "משתמשים" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/hr.po b/i18n/hr.po new file mode 100644 index 0000000..1cf735c --- /dev/null +++ b/i18n/hr.po @@ -0,0 +1,153 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Vojislav Opačić , 2022 +# Martin Trigaux, 2022 +# Vladimir Olujić , 2022 +# Bole , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Bole , 2023\n" +"Language-Team: Croatian (https://app.transifex.com/odoo/teams/41243/hr/)\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Odsutan" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kanal komunikacije" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Kreirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Kreirano" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Naziv" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM Status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Zadnji pokušaj" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Zadnja prijava" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Promijenio" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Vrijeme promjene" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Poruka" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeli" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Odspojen" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Osvježi" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Stranica se čini zastarjela." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Korisnik" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Prisutnost korisnika" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Korisnici" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "upravljanje porukama websocketa" diff --git a/i18n/hu.po b/i18n/hu.po new file mode 100644 index 0000000..6a8b0cf --- /dev/null +++ b/i18n/hu.po @@ -0,0 +1,159 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Tamás Dombos, 2023 +# Ákos Nagy , 2023 +# Martin Trigaux, 2023 +# Tamás Németh , 2023 +# gezza , 2023 +# krnkris, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: krnkris, 2023\n" +"Language-Team: Hungarian (https://app.transifex.com/odoo/teams/41243/hu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Távol" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Csatorna" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kommunikációs busz" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kapcsolat" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Létrehozta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Létrehozva" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Megjelenített név" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "Azonosító" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Üzenetküldési állapot" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Utolsó szavazás" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Utolsó jelenlét" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Frissítette" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Frissítve" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Üzenet" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modellek" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Frissítés" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Felhasználó" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Felhasználói jelenlét" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Felhasználók" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/hy.po b/i18n/hy.po new file mode 100644 index 0000000..8be929f --- /dev/null +++ b/i18n/hy.po @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Language-Team: Armenian (https://app.transifex.com/odoo/teams/41243/hy/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/id.po b/i18n/id.po new file mode 100644 index 0000000..959bd0c --- /dev/null +++ b/i18n/id.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Indonesian (https://app.transifex.com/odoo/teams/41243/id/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: id\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Menjauh" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Saluran" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus Komunikasi" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontak" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Dibuat oleh" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Dibuat pada" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nama Tampilan" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Status IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Poll terakhir" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Kehadiran terakhir" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Terakhir Diperbarui oleh" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Terakhir Diperbarui pada" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Pesan" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Model" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Luring" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Daring" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Refresh" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Halaman tersebut tampaknya sudah usang." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Pengguna" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Kehadiran Pengguna" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Pengguna" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Password Anda adalah default (admin)! Bila sistem ini terbuka ke user yang " +"tidak dipercaya penting bagi Anda untuk langsung merubahnya untuk alasan " +"keamanan. Saya akan terus mengingatkan Anda mengenai hal ini!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "penanganan pesan websocket" diff --git a/i18n/is.po b/i18n/is.po new file mode 100644 index 0000000..dde83aa --- /dev/null +++ b/i18n/is.po @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Language-Team: Icelandic (https://app.transifex.com/odoo/teams/41243/is/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: is\n" +"Plural-Forms: nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/it.po b/i18n/it.po new file mode 100644 index 0000000..0ed9665 --- /dev/null +++ b/i18n/it.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Italian (https://app.transifex.com/odoo/teams/41243/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Assente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canale" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus di comunicazione" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contatto" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Data creazione" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Stato IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Ultimo poll" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Ultima presenza" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Messaggio" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelli" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Fuori linea" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "In linea" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Ricarica" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "La pagina non sembra essere aggiornata." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Utente" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Presenza utente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Utenti" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"La password risulta quella predefinita (admin). Se il sistema viene esposto " +"a utenti non fidati, è essenziale cambiarla immediatamente per motivi di " +"sicurezza. Tale avviso verrà riproposto in modo insistente." + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "Gestione dei messaggi websocket" diff --git a/i18n/ja.po b/i18n/ja.po new file mode 100644 index 0000000..fc30898 --- /dev/null +++ b/i18n/ja.po @@ -0,0 +1,156 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Japanese (https://app.transifex.com/odoo/teams/41243/ja/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ja\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "外出" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "チャネル" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "コミュニケーションバス" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "連絡先" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "作成者" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "作成日" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "表示名" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IMステータス" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "最終返信" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "最終在席" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "最終更新者" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "最終更新日" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "メッセージ" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "モデル" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "オフライン" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "オンライン" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "リフレッシュ" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "ページが古くなっているようです。" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "ユーザ" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "ユーザプレゼンス" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "ユーザ" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"パスワードはデフォルト(管理者の)です! このシステムが信頼できないユーザーに公開されている場合、セキュリティ上の理由からすぐに変更することが重要です。" +" 私はそれについてあなたをしつこくリマインドします!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "WebSocketメッセージ処理" diff --git a/i18n/ka.po b/i18n/ka.po new file mode 100644 index 0000000..e41f5da --- /dev/null +++ b/i18n/ka.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Georgian (https://www.transifex.com/odoo/teams/41243/ka/)\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "შემქმნელი" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "სახელი" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "იდენტიფიკატორი" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "ბოლოს განაახლა" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "ბოლოს განახლებულია" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/kab.po b/i18n/kab.po new file mode 100644 index 0000000..1528ae3 --- /dev/null +++ b/i18n/kab.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Kabyle (https://www.transifex.com/odoo/teams/41243/kab/)\n" +"Language: kab\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Yerna-t" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "Asulay" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Aleqqem aneggaru sɣuṛ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Aleqqem aneggaru di" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/km.po b/i18n/km.po new file mode 100644 index 0000000..bba3542 --- /dev/null +++ b/i18n/km.po @@ -0,0 +1,150 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Sengtha Chay , 2018 +# Chan Nath , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~11.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2018-09-21 13:17+0000\n" +"Last-Translator: Chan Nath , 2018\n" +"Language-Team: Khmer (https://www.transifex.com/odoo/teams/41243/km/)\n" +"Language: km\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "មិននៅកន្លែង" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "ឆានែល" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "ទំនាក់ទំនង" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "បង្កើតដោយ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "បង្កើតនៅ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "ឈ្មោះសំរាប់បង្ហាញ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "ផ្លាស់ប្តូរចុងក្រោយ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "ផ្លាស់ប្តូរចុងក្រោយ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "អ្នកប្រើ" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ko.po b/i18n/ko.po new file mode 100644 index 0000000..f77cbce --- /dev/null +++ b/i18n/ko.po @@ -0,0 +1,156 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Korean (https://app.transifex.com/odoo/teams/41243/ko/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ko\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "자리 비움" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "채널" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "커뮤니케이션 버스" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "연락처" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "작성자" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "작성일자" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "표시명" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "메신저 상태" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "최근 투표" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "최근 출석" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "최근 갱신한 사람" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "최근 갱신 일자" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "메시지" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "모델" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "오프라인" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "온라인" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "새로 고침" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "페이지가 오래된 것 같습니다." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "사용자" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "사용자 출석" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "사용자" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"비밀번호는 기본(admin)입니다! 이 시스템이 신뢰할 수 없는 사용자에게 노출된 경우 보안상의 이유로 즉시 변경해야합니다. 나는 이 " +"문제에 대해 계속 잔소리를 할겁니다!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "웹소켓 메시지 처리" diff --git a/i18n/lb.po b/i18n/lb.po new file mode 100644 index 0000000..2650ed8 --- /dev/null +++ b/i18n/lb.po @@ -0,0 +1,146 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~12.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2019-08-26 09:09+0000\n" +"Language-Team: Luxembourgish (https://www.transifex.com/odoo/teams/41243/lb/)\n" +"Language: lb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/lo.po b/i18n/lo.po new file mode 100644 index 0000000..f89f065 --- /dev/null +++ b/i18n/lo.po @@ -0,0 +1,146 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Language-Team: Lao (https://www.transifex.com/odoo/teams/41243/lo/)\n" +"Language: lo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/lt.po b/i18n/lt.po new file mode 100644 index 0000000..be991e6 --- /dev/null +++ b/i18n/lt.po @@ -0,0 +1,161 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# grupoda2 , 2023 +# Arunas V. , 2023 +# Audrius Palenskis , 2023 +# UAB "Draugiški sprendimai" , 2023 +# Martin Trigaux, 2023 +# Jonas Zinkevicius , 2023 +# Linas Versada , 2023 +# Monika Raciunaite , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Monika Raciunaite , 2023\n" +"Language-Team: Lithuanian (https://app.transifex.com/odoo/teams/41243/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: lt\n" +"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Išėjęs" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanalas" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Komunikacijos magistralė" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontaktas" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Sukūrė" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Sukurta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Rodomas pavadinimas" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM būsena" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Paskutinė apklausa" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Paskutinį kartą matytas" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Paskutinį kartą atnaujino" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Paskutinį kartą atnaujinta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Žinutė" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeliai" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Atsijungęs" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Nuotoliniu Būdu" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Vartotojas" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Vartotojo aktyvumas" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Vartotojai" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/lv.po b/i18n/lv.po new file mode 100644 index 0000000..1ae007d --- /dev/null +++ b/i18n/lv.po @@ -0,0 +1,159 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Arnis Putniņš , 2023 +# Armīns Jeltajevs , 2023 +# ievaputnina , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: ievaputnina , 2023\n" +"Language-Team: Latvian (https://app.transifex.com/odoo/teams/41243/lv/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: lv\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Projām" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanāls" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontaktpersona" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Izveidoja" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Izveidots" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Attēlotais nosaukums" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Pēdējoreiz atjaunināja" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Pēdējoreiz atjaunināts" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Ziņojums" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeļi" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Bezsaistē" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Tiešsaistē" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Atsvaidzināt" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Lietotājs" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Lietotāji" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Jūsu parole ir noklusējuma (administrators)! Ja šī sistēma ir pakļauta " +"neuzticamiem lietotājiem, drošības apsvērumu dēļ ir svarīgi to nekavējoties " +"nomainīt. Mēs Jums par to turpināsim atgādināt!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/mk.po b/i18n/mk.po new file mode 100644 index 0000000..1c747f9 --- /dev/null +++ b/i18n/mk.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Macedonian (https://www.transifex.com/odoo/teams/41243/mk/)\n" +"Language: mk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Креирано од" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Прикажи име" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Последно ажурирање од" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Последно ажурирање на" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ml_IN.po b/i18n/ml_IN.po new file mode 100644 index 0000000..9029e5b --- /dev/null +++ b/i18n/ml_IN.po @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo 9.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2016-04-22 12:13+0000\n" +"Last-Translator: Martin Trigaux\n" +"Language-Team: Malayalam (India) (http://www.transifex.com/odoo/odoo-9/language/ml_IN/)\n" +"Language: ml_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "രൂപപ്പെടുത്തിയത്" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "അവസാനം അപ്ഡേറ്റ് ചെയ്തത്" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "അവസാനം അപ്ഡേറ്റ് ചെയ്ത ദിവസം" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/mn.po b/i18n/mn.po new file mode 100644 index 0000000..5ec3a08 --- /dev/null +++ b/i18n/mn.po @@ -0,0 +1,152 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Minj P , 2022 +# Martin Trigaux, 2022 +# Батмөнх Ганбат , 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Батмөнх Ганбат , 2022\n" +"Language-Team: Mongolian (https://app.transifex.com/odoo/teams/41243/mn/)\n" +"Language: mn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Хол байна" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Суваг" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Харилцааны цуваа" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Харилцах хаяг" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Үүсгэсэн этгээд" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Үүсгэсэн огноо" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Дэлгэрэнгүй нэр" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "ШХ Төлөв" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Сүүлийн Санал" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Сүүлийн Оролцоо" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Сүүлд зассан этгээд" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Сүүлд зассан огноо" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Зурвас" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Модел" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Оффлайн" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Онлайн" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Хэрэглэгч" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Хэрэглэгчийн Оролцоо" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Хэрэглэгчид" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/nb.po b/i18n/nb.po new file mode 100644 index 0000000..db6a46d --- /dev/null +++ b/i18n/nb.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Marius Stedjan , 2022 +# Martin Trigaux, 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Martin Trigaux, 2022\n" +"Language-Team: Norwegian Bokmål (https://app.transifex.com/odoo/teams/41243/nb/)\n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Borte" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Opprettet av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Opprettet" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Visningsnavn" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Chattestatus" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Sist kontaktet" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Sist tilstede" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Sist oppdatert av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Sist oppdatert" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Melding" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeller" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Frakoblet" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "På nett" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Bruker" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Bruker tilstede" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Brukere" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ne.po b/i18n/ne.po new file mode 100644 index 0000000..ed95afc --- /dev/null +++ b/i18n/ne.po @@ -0,0 +1,146 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Language-Team: Nepali (https://www.transifex.com/odoo/teams/41243/ne/)\n" +"Language: ne\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/nl.po b/i18n/nl.po new file mode 100644 index 0000000..326d04f --- /dev/null +++ b/i18n/nl.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Dutch (https://app.transifex.com/odoo/teams/41243/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Afwezig" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanaal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Communicatiebus" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contact" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Schermnaam" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM Status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Laatste pol" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Laatst aanwezig" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Laatst bijgewerkt door" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Laatst bijgewerkt op" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Bericht" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modellen" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Vernieuwen" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "De pagina lijkt verouderd." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Gebruiker" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Gebruiker aanwezig" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Gebruikers" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Je wachtwoord is het standaard (admin)! Als dit systeem wordt blootgesteld " +"aan niet-vertrouwde gebruikers, is het belangrijk om het om " +"veiligheidsredenen onmiddellijk te wijzigen. Ik zal er over blijven zeuren!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket berichtafhandeling" diff --git a/i18n/pl.po b/i18n/pl.po new file mode 100644 index 0000000..3aa9fe7 --- /dev/null +++ b/i18n/pl.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Polish (https://app.transifex.com/odoo/teams/41243/pl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pl\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Zaraz wracam" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanał" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Magistrala komunikacyjna" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Utworzył(a)" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Data utworzenia" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nazwa wyświetlana" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Status komunikatora" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Ostatnia ankieta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Ostatnia obecność" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Ostatnio aktualizowane przez" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Data ostatniej aktualizacji" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Wiadomość" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modele" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Niedostępny" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Dostępny" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Odśwież" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Strona wydaje się być nieaktualna." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Użytkownik" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Obecność użytkownika" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Użytkownicy" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Twoje hasło jest domyślne (admin)! Jeśli ten system jest narażony na " +"niezaufanych użytkowników, należy go natychmiast zmienić ze względów " +"bezpieczeństwa. Będę Cię o to nękał!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "obsługa komunikatów websocket" diff --git a/i18n/pt.po b/i18n/pt.po new file mode 100644 index 0000000..6899d7a --- /dev/null +++ b/i18n/pt.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Portuguese (https://app.transifex.com/odoo/teams/41243/pt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Ausente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Criado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Criado em" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nome" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Estado IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Última Votação" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Última Presença" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última Atualização por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última Atualização em" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mensagem" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Desligado" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Utilizador" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Presença de utilizador" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Utilizadores" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po new file mode 100644 index 0000000..86acb58 --- /dev/null +++ b/i18n/pt_BR.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Layna Nascimento, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Layna Nascimento, 2023\n" +"Language-Team: Portuguese (Brazil) (https://app.transifex.com/odoo/teams/41243/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Ausente" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Barramento de comunicação" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contato" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Criado por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Criado em" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nome exibido" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Status do mensageiro" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Última enquete" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Última presença" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Última atualização por" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Última atualização em" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mensagem" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Atualizar" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "A página parece estar desatualizada." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Usuário" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Presença do usuário" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Usuários" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Sua senha é o padrão (admin). Se este sistema for exposto a usuários não " +"confiáveis, é importante alterá-la imediatamente por motivos de segurança. " +"Eu vou continuar te importunando acerca disso!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "Tratamento de mensagens via websocket" diff --git a/i18n/ro.po b/i18n/ro.po new file mode 100644 index 0000000..0e726d4 --- /dev/null +++ b/i18n/ro.po @@ -0,0 +1,153 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Dorin Hongu , 2022 +# sharkutz , 2022 +# Martin Trigaux, 2022 +# Foldi Robert , 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0beta\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2022-09-22 05:45+0000\n" +"Last-Translator: Foldi Robert , 2022\n" +"Language-Team: Romanian (https://app.transifex.com/odoo/teams/41243/ro/)\n" +"Language: ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Absent" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Canal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Contact" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Creat de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Creat în" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Nume afișat" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Status IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Ultimul sondaj" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Ultima prezență" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Ultima actualizare făcută de" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Ultima actualizare pe" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mesaj" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modele" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Deconectat" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Activ" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Actualizare" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Pagina pare să nu fie actualizată." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Operator" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Prezență utilizator" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Utilizatori" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po new file mode 100644 index 0000000..c3cb1af --- /dev/null +++ b/i18n/ru.po @@ -0,0 +1,160 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# ILMIR , 2023 +# Alena Vlasova, 2023 +# Martin Trigaux, 2023 +# Wil Odoo, 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2024\n" +"Language-Team: Russian (https://app.transifex.com/odoo/teams/41243/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Нет на месте" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Канал" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Коммуникационная шина" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Контакты" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Создано" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Создано" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Отображаемое имя" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Статус IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Последний опрос" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Последнее присутствие" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Последнее обновление" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Последнее обновление" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Сообщение" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Модели" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Не в сети" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Онлайн" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Обновить" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Похоже, что эта страница устарела." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Пользователь" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Присутствие пользователя" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Пользователи" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Ваш пароль по умолчанию (admin)! Если эта система подвергается воздействию " +"недоверенных пользователей, важно немедленно изменить настройки по " +"соображениям безопасности. Я буду продолжать докучать Вам по этому поводу!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "обработка сообщений в сокетах" diff --git a/i18n/sk.po b/i18n/sk.po new file mode 100644 index 0000000..12a9462 --- /dev/null +++ b/i18n/sk.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Slovak (https://app.transifex.com/odoo/teams/41243/sk/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sk\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Neprítomný" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanál" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Komunikačná zbernica" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Vytvoril" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Vytvorené" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Posledný prieskum" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Posledná prítomnosť" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Naposledy upravoval" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Naposledy upravované" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Správa" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modely" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Obnoviť" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Zdá sa, že stránka je zastaraná." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Užívateľ" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Užívateľova prítomnosť" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Užívatelia" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/sl.po b/i18n/sl.po new file mode 100644 index 0000000..f97a29d --- /dev/null +++ b/i18n/sl.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# laznikd , 2023 +# Matjaz Mozetic , 2023 +# matjaz k , 2023 +# Martin Trigaux, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Martin Trigaux, 2023\n" +"Language-Team: Slovenian (https://app.transifex.com/odoo/teams/41243/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Odsoten" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Stik" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Ustvaril" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Ustvarjeno" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Prikazani naziv" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Zadnje glasovanje" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Zadnja prisotnost" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Zadnji posodobil" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Zadnjič posodobljeno" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Sporočilo" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeli" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Brez povezave" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Spletno" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Uporabnik" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Uporabniki" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/sq.po b/i18n/sq.po new file mode 100644 index 0000000..fbb9c30 --- /dev/null +++ b/i18n/sq.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Martin Trigaux , 2017\n" +"Language-Team: Albanian (https://www.transifex.com/odoo/teams/41243/sq/)\n" +"Language: sq\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Krijuar nga" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Emri i paraqitur" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Modifikuar per here te fundit nga" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Modifikuar per here te fundit me" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/sr.po b/i18n/sr.po new file mode 100644 index 0000000..0848b7d --- /dev/null +++ b/i18n/sr.po @@ -0,0 +1,160 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux, 2023 +# Dragan Vukosavljevic , 2023 +# Milan Bojovic , 2023 +# コフスタジオ, 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: コフスタジオ, 2024\n" +"Language-Team: Serbian (https://app.transifex.com/odoo/teams/41243/sr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sr\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Daleko" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Komunikacioni Bus" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Kreirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Kreirano" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Naziv za prikaz" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM Status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Poslednje istraživanje." + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Poslednja prisutnost" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Poslednji put ažurirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Poslednji put ažurirano" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Poruka" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeli" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Osveži" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Stranica izgleda zastarelo." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Korisnik" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Prisustvo korisnika" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Korisnici" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Vaša lozinka je podrazumevana (admin)! Ako je ovaj sistem izložen " +"nepoverljivim korisnicima, važno je da je odmah promenite iz bezbednosnih " +"razloga. Nastaviću da vas podsećam na to!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket rukovanje porukama" diff --git a/i18n/sr@latin.po b/i18n/sr@latin.po new file mode 100644 index 0000000..00b9faf --- /dev/null +++ b/i18n/sr@latin.po @@ -0,0 +1,152 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Martin Trigaux , 2017 +# Djordje Marjanovic , 2017 +# Ljubisa Jovev , 2017 +# Nemanja Dragovic , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2017-09-20 09:53+0000\n" +"Last-Translator: Nemanja Dragovic , 2017\n" +"Language-Team: Serbian (Latin) (https://www.transifex.com/odoo/teams/41243/sr%40latin/)\n" +"Language: sr@latin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Odsutan" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Kreirao" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Naziv za prikaz" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Zadnja dojava" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Zadnji put prisutan" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Promenio" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Vreme promene" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Poruka" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Prisustvo korisnika" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Korisnici" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/sv.po b/i18n/sv.po new file mode 100644 index 0000000..c14b7a5 --- /dev/null +++ b/i18n/sv.po @@ -0,0 +1,166 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Moa Nicklasson , 2023 +# Simon S, 2023 +# Robin Calvin, 2023 +# Jakob Krabbe , 2023 +# Simon Nilsson, 2023 +# Mikael Carlsson , 2023 +# Kim Asplund , 2023 +# Martin Trigaux, 2023 +# Chrille Hedberg , 2023 +# Anders Wallenquist , 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Anders Wallenquist , 2024\n" +"Language-Team: Swedish (https://app.transifex.com/odoo/teams/41243/sv/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Borta" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Kommunikationsbuss" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Skapad av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Skapad den" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Visningsnamn" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM-status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Senaste undersökning" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Sågs senast" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Senast uppdaterad av" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Senast uppdaterad den" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Meddelande" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeller" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Frånvarande" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Uppkopplad" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Ladda om" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Sidan verkar vara föråldrad." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Användare" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Användarens närvaro/status" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Användare" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Ditt lösenord är standard (admin)! Om systemet utsätts för otillförlitliga " +"användare är det av säkerhetsskäl viktigt att omedelbart ändra lösenordet. " +"Jag kommer att fortsätta tjata på dig om det!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket meddelandehantering" diff --git a/i18n/ta.po b/i18n/ta.po new file mode 100644 index 0000000..11fe785 --- /dev/null +++ b/i18n/ta.po @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo 9.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-16 13:49+0000\n" +"PO-Revision-Date: 2016-02-11 12:51+0000\n" +"Last-Translator: Martin Trigaux\n" +"Language-Team: Tamil (http://www.transifex.com/odoo/odoo-9/language/ta/)\n" +"Language: ta\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "சேனல்" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "உருவாக்கியவர்" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "காட்சி பெயர்" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "கடைசியாக புதுப்பிக்கப்பட்டது" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "கடைசியாக புதுப்பிக்கப்பட்டது" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "பயனர்கள்" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!" +msgstr "" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "" diff --git a/i18n/th.po b/i18n/th.po new file mode 100644 index 0000000..fbe28d3 --- /dev/null +++ b/i18n/th.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Rasareeyar Lappiam, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Rasareeyar Lappiam, 2023\n" +"Language-Team: Thai (https://app.transifex.com/odoo/teams/41243/th/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: th\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "ห่างออกไป" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "ช่อง" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "รถบัสสื่อสาร" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "ติดต่อ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "สร้างโดย" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "สร้างเมื่อ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "แสดงชื่อ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ไอดี" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "สถานะ IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "โพลครั้งล่าสุด" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "การแสดงตนครั้งสุดท้าย" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "อัปเดตครั้งล่าสุดโดย" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "อัปเดตครั้งล่าสุดเมื่อ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "ข้อความ" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "โมเดล" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "ออฟไลน์" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "ออนไลน์" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "รีเฟรช" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "ดูเหมือนเพจจะล้าสมัยแล้ว" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "ผู้ใช้" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "การแสดงตนของผู้ใช้" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "ผู้ใช้" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"รหัสผ่านของคุณเป็นค่าเริ่มต้น (admin+)! " +"หากระบบนี้เปิดเผยต่อผู้ใช้ที่ไม่น่าเชื่อถือสิ่งสำคัญคือต้องเปลี่ยนทันทีด้วยเหตุผลด้านความปลอดภัย" +" ฉันจะจู้จี้คุณเกี่ยวกับเรื่องนี้!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "การจัดการข้อความของ websocket" diff --git a/i18n/tr.po b/i18n/tr.po new file mode 100644 index 0000000..f20d513 --- /dev/null +++ b/i18n/tr.po @@ -0,0 +1,166 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Ramiz Deniz Öner , 2023 +# abc Def , 2023 +# Nadir Gazioglu , 2023 +# Ediz Duman , 2023 +# Ozlem Cikrikci , 2023 +# Tugay Hatıl , 2023 +# Levent Karakaş , 2023 +# Martin Trigaux, 2023 +# Murat Durmuş , 2023 +# Murat Kaplan , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Murat Kaplan , 2023\n" +"Language-Team: Turkish (https://app.transifex.com/odoo/teams/41243/tr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Dışarıda" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kanal" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "İletişim Veriyolu" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Kontak" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Oluşturan" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Oluşturulma" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Görünüm Adı" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Anlık İleti Durumu" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Son Anket" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Son Durum" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Son Güncelleyen" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Son Güncelleme" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Mesaj" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Modeller" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Çevrimdışı" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Çevrimiçi" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Refresh" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Sayfa güncel değil gibi görünüyor." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Kullanıcı" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Kullanıcı Durumu" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Kullanıcılar" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Şifreniz varsayılan (admin)! Bu sistem güvenilmeyen kullanıcılara maruz " +"kalırsa, güvenlik nedenleriyle derhal değiştirilmesi önemlidir. Bu konuda " +"seni rahatsız etmeye devam edeceğim!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket mesaj işleme" diff --git a/i18n/uk.po b/i18n/uk.po new file mode 100644 index 0000000..b98178a --- /dev/null +++ b/i18n/uk.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Martin Trigaux, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Martin Trigaux, 2023\n" +"Language-Team: Ukrainian (https://app.transifex.com/odoo/teams/41243/uk/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: uk\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Відійшов" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Канал" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus комунікація" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Контакт" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Створив" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Створено" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Назва для відображення" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Статус у миттєвих повідомленнях" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Останнє опитування" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Остання присутність" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Востаннє оновив" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Останнє оновлення" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Повідомлення" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Моделі" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Офлайн" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Онлайн" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Оновити" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Сторінка, здається, застаріла." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Користувач" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Присутність користувача" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Користувачі" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Ваш пароль за замовчуванням (admin)! Якщо ця система наражається на " +"ненадійних користувачів, важливо її негайно змінити з міркувань безпеки. Ми " +"будемо тримати вас в курсі!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "обробка повідомлень websocket" diff --git a/i18n/vi.po b/i18n/vi.po new file mode 100644 index 0000000..2f9e79d --- /dev/null +++ b/i18n/vi.po @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# Thi Huong Nguyen, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Thi Huong Nguyen, 2023\n" +"Language-Team: Vietnamese (https://app.transifex.com/odoo/teams/41243/vi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: vi\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "Vắng mặt" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "Kênh" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "Bus thông tin trao đổi" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "Liên hệ" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "Được tạo bởi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "Được tạo vào" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "Tên hiển thị" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "Trạng thái IM" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "Lần thăm dò cuối cùng" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "Hiện diện lần cuối" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "Cập nhật lần cuối bởi" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "Cập nhật lần cuối vào" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "Thông báo" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "Mô hình" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "Offline" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "Online" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "Làm mới" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "Trang này có vẻ đã quá cũ." + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "Người dùng" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "Sự hiện diện của người dùng" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "Người dùng" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "" +"Mật khẩu của bạn đang là mặc định (admin)! Nếu có người dùng không đáng tin " +"cậy sử dụng hệ thống này, thì việc quan trọng là đổi mật khẩu ngay lập tức " +"để bảo mật thông tin. Chúng tôi sẽ tiếp tục nhắc bạn về việc này!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "xử lý thông báo websocket" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po new file mode 100644 index 0000000..bf77e9d --- /dev/null +++ b/i18n/zh_CN.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Chinese (China) (https://app.transifex.com/odoo/teams/41243/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "离开" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "频道" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "通讯总线" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "联系人" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "创建人" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "创建日期" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "显示名称" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "ID" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM的状态" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "最后在线" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "最后登录" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "最后更新人" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "上次更新日期" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "消息" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "模型" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "离线" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "线上" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "刷新" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "‎该页面似乎已过期。‎" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "用户" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "用户上线" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "用户" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "您的密码是默认密码(admin)! 如果此系统暴露给不受信任的用户,则出于安全原因立即更改它很重要。 我会继续唠叨您!" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "websocket消息处理" diff --git a/i18n/zh_TW.po b/i18n/zh_TW.po new file mode 100644 index 0000000..6041e57 --- /dev/null +++ b/i18n/zh_TW.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bus +# +# Translators: +# Wil Odoo, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 23:09+0000\n" +"Last-Translator: Wil Odoo, 2023\n" +"Language-Team: Chinese (Taiwan) (https://app.transifex.com/odoo/teams/41243/zh_TW/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__away +msgid "Away" +msgstr "離開" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__channel +msgid "Channel" +msgstr "群組" + +#. module: bus +#: model:ir.model,name:bus.model_bus_bus +msgid "Communication Bus" +msgstr "通信匯流排" + +#. module: bus +#: model:ir.model,name:bus.model_res_partner +msgid "Contact" +msgstr "聯絡人" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_uid +msgid "Created by" +msgstr "建立人員" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__create_date +msgid "Created on" +msgstr "建立於" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__display_name +#: model:ir.model.fields,field_description:bus.field_bus_presence__display_name +msgid "Display Name" +msgstr "顯示名稱" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__id +#: model:ir.model.fields,field_description:bus.field_bus_presence__id +msgid "ID" +msgstr "識別號" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__status +#: model:ir.model.fields,field_description:bus.field_res_partner__im_status +#: model:ir.model.fields,field_description:bus.field_res_users__im_status +msgid "IM Status" +msgstr "IM的狀態" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_poll +msgid "Last Poll" +msgstr "最後線上" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__last_presence +msgid "Last Presence" +msgstr "最後出席" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_uid +msgid "Last Updated by" +msgstr "最後更新者" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__write_date +msgid "Last Updated on" +msgstr "最後更新於" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_bus__message +msgid "Message" +msgstr "消息" + +#. module: bus +#: model:ir.model,name:bus.model_ir_model +msgid "Models" +msgstr "型號" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__offline +msgid "Offline" +msgstr "離線" + +#. module: bus +#: model:ir.model.fields.selection,name:bus.selection__bus_presence__status__online +msgid "Online" +msgstr "網上進行" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "Refresh" +msgstr "重新載入" + +#. module: bus +#. odoo-javascript +#: code:addons/bus/static/src/services/assets_watchdog_service.js:0 +#, python-format +msgid "The page appears to be out of date." +msgstr "該頁面似乎已逾時" + +#. module: bus +#: model:ir.model,name:bus.model_res_users +msgid "User" +msgstr "使用者" + +#. module: bus +#: model:ir.model,name:bus.model_bus_presence +msgid "User Presence" +msgstr "使用者出現" + +#. module: bus +#: model:ir.model.fields,field_description:bus.field_bus_presence__user_id +msgid "Users" +msgstr "使用者" + +#. module: bus +#. odoo-python +#: code:addons/bus/controllers/home.py:0 +#, python-format +msgid "" +"Your password is the default (admin)! If this system is exposed to untrusted" +" users it is important to change it immediately for security reasons. I will" +" keep nagging you about it!" +msgstr "您的密碼是預設(admin)!如果此系統向不受信任的使用者公開,出於安全原因,非常重要請立即更改! (將一直提醒!)" + +#. module: bus +#: model:ir.model,name:bus.model_ir_websocket +msgid "websocket message handling" +msgstr "WebSocket 訊息處理" diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..f309e7d --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from . import bus +from . import bus_presence +from . import ir_model +from . import ir_websocket +from . import res_users +from . import res_partner diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c820604b3a7aeda33def7ef4ac02ef809efbadf0 GIT binary patch literal 457 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 literal 0 HcmV?d00001 diff --git a/models/__pycache__/bus_presence.cpython-311.pyc b/models/__pycache__/bus_presence.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36a23475ae6e3303fa27b4011dade2402af7faf7 GIT binary patch literal 5102 zcmcH-OKcm*b(WvyQY1x6qAkUa<&9)nW-Lp!Q`L#TkSt1(V^azx*%pgrz2dB-l}RqM zyOeB}j)}krBQOvl5b7LSKv+M)$oU@0&Vu_pmV55Cbz0vI{<%`8cg zw1S*E9KM}-@9mqJ_vXEMvw!sYS`aCJ8CcBw5c&`8RDiSc{O)sTJVF>@CV^tslZi3V zw5X}z?TYy%ALgqC75uFdYs1V83VENCZY0E*fvL2lin^?%#6(zC z6*Xix?P6Ls*+p4OV9jKc3YHQYusU@`p^fh83E|RIP@GJrT577} z8KOD~5p~6En2Fi3E#|=Xm=il26cNh)7OweXcPscXEZi+V=m zWsUDwQVBUF^$+m<;+lA;pI21CEiO{zAV0edJk;>Kwyb0lm|u{1kyldEDfkcY3mKhA zNhx|wzNqVBd|AT0uJAA&Ap^6}NJm^%WXxkx7Z*fL;^Rsxo>5gYU0xy^P8;NBWYW`@ zCB8qC22M#xS60-|CnbGZ!DKY)wiM6k60RGPmtagjtt1iyyu3&yka78CaaH1znS?H< z6B4har4*T%xS$Q}F+Lv`Q}9L_xUjV}QH7Z#$yg^6%F>dA`v>WuS?@wd8?4SP>ub>T zc-9S*9;E9worI*BuDGbhMJ$0!a7qgNNYbUO`--UP{4`8D4(6&AvLT6|es=-DBc#^| z2(8rkHf!6qj@-1hX3!HR^2DwZPcpeNP^T^@CDWc#)}An?Lz5DV8WG6(Z3FDkIrq4&J{+l7e}w>(RYAHpcogH7DX^s)@SLZV)_4J8jO=(p#v%UtyWwbrmNz{ zCM$qLCZuL7m<~B5>)Am4ej*s69&G|Z9+f!nL++txn>)D09V{bs=#mZKxb3p-d-8bS z_5yGex}yXI5a>;iS}jg=TrHp_OW)T(Pmhor={0@TyC7y7bvcX}u+83%nPYOcoc*b- z@q{4!yVhB$&0({8_El#@pN;1RSk6vPM$bnmXv zBS<}c*O7Cqz}agRy_PN@w2IW;pO~M!%3uF%oL}5F4|BQe_}DRnKB=_Um^Eb0DdfoB z=jUjMhqD3VeF8$EK;kzqrf{WK2>`re1$67Q(CNOag?@6Vok@$TnB?V@7}w=hS-&Gd zDkCfS439HbN&_@LDJK%LCc&|Twc6`h4+(aI+D8ciFF?lA_KFa*RmM_ECsJu5RM%e> z6B#KIYEg;$ObW zThHeCLfgq=+sQJr`}!y-1%lgwzO6u?@%E$@&^tD_%d?1+jl|9 zA9#5C+k@{Ieo}Wj4{qGv_|10b@vY9|h0YVj&J#wUxt6?bFxJbm8w9~q8wdS-5*+DA zzdb#2!2ZnVf#$OVj*)KHvu-zqa*6I@DIVO*3L0c9gX2D z3+eG)CwA&}&b>nBPD_2*Xnap*?y@wf`R62bLvrCv$l!C(@Ycrx(_M=@rq60t zf=;N#WHm`tb*lsrx{d=_XCc;B?V+(0q#1baj!^$?U+rRS8~D&ybFDO_g;i z%sW?g@U)IbdCw%|Qn^d2^qxFUbgBPPAG!vM{AnM;%u>^#yjd>=-nB}jhgiE0lAw@1+giz52I*NMsl)KX zc*v%ng{R48NIG}xl}sfdEX9TT?5N3FP1MUEOCzBjti^dh^L*+t_Pu2Ue_7y$i`?)w zcW#S2SKuxbxeG?MQjJ95md6P~N=!NhCS9qPZ?vCm) zLUaHyo%BwizC{pZtC}rhMpstFcqWrnNlblWWyV%+O)kzV0pw1sv0$Ej?N_!roE(I7 z0AA!fm=Bjadh%CF?Vb7ec3KZTSbV5_aqG*|f1Wp5Ckm|-#ny@ZL@981o!#(ndW_-g zqy}yj0ym0*8~Mpnuy=iYV`_8CI4_VIyjcj|ECz4pua4jJ7aR_e`LM&@I~ zcdy{PSM=S>kCz>cljMqZkVUNf{(RZi=7d8BzzaKK{Z+)#3eoTEEh7LmB0=;J;)i7$ z< ZrOcjXz!tB9^`jdDF9`noJ&1Iv{{y(p+y(#u literal 0 HcmV?d00001 diff --git a/models/__pycache__/ir_model.cpython-311.pyc b/models/__pycache__/ir_model.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed61bccbc05cf2e72e2f0b82cee6356e6513e154 GIT binary patch literal 2567 zcmbVO%}*Og6rb5$8{_a9NbH0rbtoTkXl#g-w4sEA7SyCwh$@t(bwjttyAJEvUNUQt zKx@h&Qi;TYq^aQ018SvIRU-8tXr&x_NN?-NQmj3->Z!LyAT{FD_hyX^A+6dD>$h)a z-h1;tes7lV>g)Xk$4{>(A9)G+olnZdQ+{u}3Cs#nh(gmOLt~T@fxJr;;Wkl3m8OJk zr3WG*58>yIn5al&B<%SEr(tS)GrFRtjYN5yzi4@~5d>z1s3b-e5)%{(%3wYwDI%DV z?3zpLGHTEHG#24@5|vXy6Fet)?#4G@@C&*^G?EwY&|xzEBrlpI1zYYg#Y5XH$xDkg zNfmnAXG&|^j#PG$nS>a>rH{l1u=}tgv9|N%iu?oM9#&?^V4>Y82{Lo=iQ}|L?Iu6HD+sD*&Jg4bd;Kdti4xlAO zdTmcK8_%e=KdGr{MONauxZTQ8CgZvx=X5!#<`R@ik>y16Vpc2MSWCbGUp;wvs?ZFMN8xodS+^H?o=7s5gFWZMPx-y zYFQ24G$I3OJrPeEgORFE12|!P2;}(!aY$`PLzVp2+POTk+V9knVEfAaz4^6NA<$P0 z^f}Ea)Jbio1Ic^uaRR~ z_6DXf4#A~onGZ!^dTUx^s$$m~YFbU8*LHI?0-31^ohf!;&yviu4TbQ>b6|R6I;R@D zV1dczu;>B?VR3^1Eb8;rq}7hTBS4Y&ne?u()G$WA$0GZyr3;G;?>8tgR+iNRZ(vJ7 z2^pplN|Dx#|6GcS&S5lo3J5QS*mck$+oaUqU1|#z+j^|%J60P$jz|t2EpZlRtO?X! z9`AA3!8;71ZQLC#L|$ne0Cm$CVQnBB7T7EBV$Cj(A+sYu3~bg=h=FYo8MqyOCRXvw$)H3mSHLhY9CNa=9IA%WTv>e9vcO0B{B7guA&*5ivKjiT-usIkA5e$)A4G61>-WxBGs#(+F19@g~_sV%3G5 zR?^k~tn>WS&hv%NpbG9I zPzVl{0>{?6ih;0oa_DJb$O;UV&b|HP_>;`(j} z;dhE5!Ec;_UAj>DD>WWkxadfSJ)I8OOI#zUItrg03BMN%?;+cB8UL-}MqfB>%QxUU sImpg{B(}YA1&AY3N*ynuL5l=-K92VYg~Ruf#pF_Y9oIKsvdKOB3({YZ!~g&Q literal 0 HcmV?d00001 diff --git a/models/__pycache__/ir_websocket.cpython-311.pyc b/models/__pycache__/ir_websocket.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77e10c86ebd9635f71376f675098beea4a69fe66 GIT binary patch literal 5264 zcma)AO>7&-6`tkq@<$@|6Uma5go;JiOdKkyW!Z@p%V{M!{)y}+Mw-xZ7c0(4T50){ z*`;N%bfE@5D1m^eKm+^W0y;RZ3#)(+J><{;IrLBzEwEG<5V3$xMS%vr&{cv0KK0Em zx!jc%pu^qA`FZo^&Fp*Md%M4FYYQV*zB)Sl3#k3WY&3|!@xLoWAF*mDb2&;s;Z1#^APD%#-es-uFv zo4)%n^3WdLaIR%@Vm}U4IH$Z>?;MANbKHI4e`wE{*t7A92lHbfrQ%`RpDsrzQPM?` zs(C`Q_L{)JEUF9c-RFSZMsu)_YvG%mgeu$@aO{|q;Qv>iwFt=Z7(R8mNQ_`Z(C=86 zC;f`S6&0pQBJpH=c)pM$!FwhQyn6c}Yh%&O9LBB;!=JragHZ-nfHjo@WMl|o)j zc?~bgUpJylk~%MB3wf2?P>qN}M4Fu!C=oGm_!J_~8a?)GLBfhKvn-gKr_4wcW>Sen zg0hETw7Yj%?*IN1h_cD6Kvqz#yJt02iyY7feyBy*RrfTv|26*8{GV^?Q`feq7Ph7q zw55tRwNRaco}2$g=qz{M^B?Q4*4cB%r#(0F=$iiAkF|~oEk0T67+7z6?DwAK+P_7N zng{im9WAtnVyoxYrD`-$=TW>*;}6hhKpB_Eu&p^fMuldx6a%Vly9hj~!hMhZb8au; zz@itJOtUQbnl(D~eG+E^!6U_-E%Eil6*kq<(9)PqO3W-fWTtHiMrei>M4S~B zHN{hQrZfb^2#CcZ$zvm^Som&uOA@9B*^sZK5Nd=QKmcZ)@=}H%2H!jv!wcB%w(eeG zrX^e$7`|P)!5S(%k0Cn*e?Mi zZzI)#l$ZmI=dh=R?0YfSZAnT@we1uk>~)WR%z<6n9)Num=NJbGL4K z)sEK81!h=gsd)Ej;y8~5D+2<)R#u`OXiQ|iwubjX`-k>vu&f8XH$44Rh&u+ecFzF0 zfH-)t5ajx9a?ZzF;hccv4zBN^+Z>11`c2{7Gz7h{q)2&)MENW+yoyRw9?CXpaM?y6 zG~xV7Xl#z;$&DgCT~4`7+y)Fs9BZ-;fgt55kbl8%1!4omgk%Y}H5S_2F@7R6Eb;Kx1e0*cpZ& z=eeEE0qxnhs-5Tb&U0GyTrHWp_x=M(PoCUPzOj{jqnaGoljGaTsjcMH*Hrt-J6a~I zC8w%Mtg~xQOUl}!tnn|@BC++r+L+cqvK1N8A|thf1FIA3J)d0M>FQZmw|kFm^&WdP z@nye0GEwcF)O#nZT~m73)GEKj$92B{&dH4{4-Vh^@X^R0PU^=d^kWzQCjC{e@>g~K zs@A+LICH`W3w*4-d)b6DXNtODLmT~k|Ni|0vKxq$W#?RHnz}!XG1zdP?TtF(LnUsb``&?j&u$OCwl(-# zb?}rvc*^RpMdNF4t^Z7q4sAzAwxT1o_+ah%Bemxb-Mw+QQV*iGfBWdDT0Za+WGC+RkKI5-qQ?y#@BhK*#i|i>6mO(^`iu*}?snAE=HH z+Oqil=ec@q`*+79CGFy}b=%n2)-kua$7498vUjCMaeMT~@Y44#a>=)~*kC~ZRN0%_ zxZ|oL;B&38zW3#~5xD)+SKtjSxJXD*z%%9ejHr;b)3pxb4Sbo4Z zZnS0RNp=yO!TcCu#if8;m@AZ2%J#rA(pe+iFk5(cGvgFEt2ZkvM#Qm>Gl&~)f>?q~ z9K0EV4=MM#e84$$0@hI0fUKZ@M!NOLfF4O`k)u0Z2lcMh?^F7Vr@s2&kJqYQ7xbwM#WhV}XI=CG_vK2q_s9TF4sm4e3_^1{i-RXI`+B2;83~TY>x*u3g{_l8naOm#K z+x;(X^}nPYJzeb|*Zap;FYH90(W8lt#d~?kZ0!^m>TsFnJDrLEN|=#W5CB%#`)5fe zP>%}2MuRgR?6ve_Dov-_3?KU{L74*^Ay!S#t7_4D>oKh)YIcIPU6-tSwF0I9)8RQzTIckJh%S=BP86V literal 0 HcmV?d00001 diff --git a/models/__pycache__/res_partner.cpython-311.pyc b/models/__pycache__/res_partner.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c0b21be97b44fe8defab25a855aff849fe0bcbc GIT binary patch literal 2396 zcmcH*OKcNIbavPOI3E$9g^+606eX^rbts5dZHVO9PH;)q$c~|yhSlO3la1DEcX!Pv z3Q`UnsFVW-q;Nz%1ge0c}y@4cD# zoi}g&sIxPI*!Xd{Jm5#@Plsp$pq;Vx6}(muL4?y#g)4FuzQ|XEqQEg8ulXweqTj&< zzyrkqhj=uJi0=*}ek#ldTYAu{fY2IDcN9Y;kVmoL-;9h2wxHBhJ5W}sMoc?UsS>K0 zG0u)8E+#I?Gnwgh4yfK#CZEhoQaU-4$x1G})KciYd@W*Y6p$4}QIR94$P=z8@QC{6 zw}S%5iSWd?%7aTV-BAn>{~c5ex{leA95v4?hNV+up~Il2SZfjC%rwqhid8r519GWa zsnsnis}BA><=&!#0@QEm>6i<)9ua;>UqF`UU`sW3H#XV~gswOrOzoeu8Nz$`fm zlxuWIuihAn;lvDP#fytsDUAmUGs(f>J?dk446C|Djq8erkDLF46{udSmuPH%OUe!9 z*8ahyrywPRRek?((^87%(k=V8WK86;)A(xLlxr=^c)V>rmzg{}gTKsXBn%tmx=9Ta z7jRa><02#2DV>2sQ%Rc`U9c&gVwHaCUe=sc|3lpP0mxh z5CL(MmZ~~2+pa6?Bqw2Rod$3Yan6;UJqSe%9pwn{+yNA~LzVdpXocg@@*%=QfeJRS z5sR95wj_>f)smu_W1=U~$NU%p@E81+P^0$> zJU<-1ci}!$mXqqitqn6Rdc7}J_s9^g{s+Da4BDRAqLniB> zaLbGXOgU>73;q8Yb|-8xp8{Ay8=>fOXu0EMsP9FnZxcoPqnq!e?!MpqN7lPWo(=ze zbp4ZazZDw!i_QGyU*uoqM(6CSBhj$Ty+#az1i*ieqV8Td7eAeP{@L>{UM4TUNM2q~ z&Nh>?z+dmZ(gfHgH@oD!!p3`GhzpN{FoSm|H}Jrt;CI2t!6%VtQ|ma<#EH9;&1iok z+P^96Y%~sn$5Je0hh$k-DpZ#3h%7?|)HQ}jW%--BqP12+vZ~KfL$wT6v$iierx?Zu zfI9gxdKkv^xjAEv*I11o{479#8PI{ z*W+h#>2z?8|0;3jh+xahn0e9dDqI-t}yt&l~N}21+*CpUuD_j)l5Eym$Ei Ou~*L8>v!yMlJPIc`aS0W literal 0 HcmV?d00001 diff --git a/models/__pycache__/res_users.cpython-311.pyc b/models/__pycache__/res_users.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b9195b33b0a58889a632d2a5581daad8476681f GIT binary patch literal 2203 zcma)7O-vg{6rSC+jm^Knty&P(sw4te$c83LX@jVcZ5oTPNEkwEN>&@s7#6L)?(Ui- zE>Ssf;NU|Jk%)RJhl-@4haP*W)I)lx)Jk^bDrinfy|gz+q!&(ovuhR{pz7?*n>TM} z-n{qbd$Uj4+L{q7KMiHBdl3559@L4q-XDGn%r?RZa~jHWDK5*W_^gl;I40vYPu83A z+Pna~FXiJ9k3K=zvx%^m2&+xUN_Pc>_MkZ><;T7x3N`)Bh9Q9pN?xVDj7l_YQeRfb zL^DGiZJt<|_)MNlOvh(o)E!GCqcf5ekIp4#BuAXCE3`Xs!Y+qn&}}1vQXEDp9&;&y zN5r#w>=YP|h3`GPJeUN{DJdWJZlY9^V;J?%5_8@phFJ_4#0(c4HZd(GEyXIBv`bFw z*?hqwvYM4mTLvaJ)rD#9SS|E^Xw37&7)ZC#I$CunSKW0xt|9IuFqp%<iG zST9UswH%KS)9$X{1?_w82^pc$VGdaxC;PCld68wOPCHq@3+$ddjG@MH^wyxt)N!=@hfhuR2%+8Gh|z)_K|WuhfsdfE!=&k{ETuFIp=^bgw|vGq%9KE>Zas zoNpFSm1O)Tpr=8^X4!IC#iC-0DjqsB9i2$V#f2MjNmQ1}U{1d~7!oJuM21T-H6z8v zf%&=Uz>ugZrX}ZfO$&+RqMEaav8HI^WmBAEdk6GPMpJWS;EaCT!2hiu;7D?5a{d|0 zUFH4-2gGlJ7Xy0kd>i6YjJfY1xxvE8*_mnaPQjG(^^+TB!R*<%8^DAF?z;2EX7Jr4 zX+4L{hKyi|dIj3yH6T-nv$J9;2snX+eH&y>ff08y%J8?)HpijT8<+p}Cm)(JY%)LU4!VaMS1W`w~6NI0yJ{x^@{Y7-~d33Q7U8+WxKwjy-T?N`M zSKH++VL$j*4RL`FZL$^H?-RdS_^EV4 w1q33;aWx;}E|<}zi|Ri5sN7ihQMBAxYrY@{UY;deYo+y 1: + _logger.info("The imbus notification payload was too large, " + "it's been split into %d payloads.", len(payloads)) + for payload in payloads: + cr.execute(query, (payload,)) + + @api.model + def _sendone(self, channel, notification_type, message): + self._sendmany([[channel, notification_type, message]]) + + @api.model + def _poll(self, channels, last=0): + # first poll return the notification in the 'buffer' + if last == 0: + timeout_ago = datetime.datetime.utcnow()-datetime.timedelta(seconds=TIMEOUT) + domain = [('create_date', '>', timeout_ago.strftime(DEFAULT_SERVER_DATETIME_FORMAT))] + else: # else returns the unread notifications + domain = [('id', '>', last)] + channels = [json_dump(channel_with_db(self.env.cr.dbname, c)) for c in channels] + domain.append(('channel', 'in', channels)) + notifications = self.sudo().search_read(domain) + # list of notification to return + result = [] + for notif in notifications: + result.append({ + 'id': notif['id'], + 'message': json.loads(notif['message']), + }) + return result + + def _bus_last_id(self): + last = self.env['bus.bus'].search([], order='id desc', limit=1) + return last.id if last else 0 + + +#---------------------------------------------------------- +# Dispatcher +#---------------------------------------------------------- + +class BusSubscription: + def __init__(self, channels, last): + self.last_notification_id = last + self.channels = channels + + +class ImDispatch(threading.Thread): + def __init__(self): + super().__init__(daemon=True, name=f'{__name__}.Bus') + self._channels_to_ws = {} + + def subscribe(self, channels, last, db, websocket): + """ + Subcribe to bus notifications. Every notification related to the + given channels will be sent through the websocket. If a subscription + is already present, overwrite it. + """ + channels = {hashable(channel_with_db(db, c)) for c in channels} + for channel in channels: + self._channels_to_ws.setdefault(channel, set()).add(websocket) + outdated_channels = websocket._channels - channels + self._clear_outdated_channels(websocket, outdated_channels) + websocket.subscribe(channels, last) + with contextlib.suppress(RuntimeError): + if not self.is_alive(): + self.start() + + def unsubscribe(self, websocket): + self._clear_outdated_channels(websocket, websocket._channels) + + def _clear_outdated_channels(self, websocket, outdated_channels): + """ Remove channels from channel to websocket map. """ + for channel in outdated_channels: + self._channels_to_ws[channel].remove(websocket) + if not self._channels_to_ws[channel]: + self._channels_to_ws.pop(channel) + + def loop(self): + """ Dispatch postgres notifications to the relevant websockets """ + _logger.info("Bus.loop listen imbus on db postgres") + with odoo.sql_db.db_connect('postgres').cursor() as cr, \ + selectors.DefaultSelector() as sel: + cr.execute("listen imbus") + cr.commit() + conn = cr._cnx + sel.register(conn, selectors.EVENT_READ) + while not stop_event.is_set(): + if sel.select(TIMEOUT): + conn.poll() + channels = [] + while conn.notifies: + channels.extend(json.loads(conn.notifies.pop().payload)) + # relay notifications to websockets that have + # subscribed to the corresponding channels. + websockets = set() + for channel in channels: + websockets.update(self._channels_to_ws.get(hashable(channel), [])) + for websocket in websockets: + websocket.trigger_notification_dispatching() + + def run(self): + while not stop_event.is_set(): + try: + self.loop() + except Exception as exc: + if isinstance(exc, InterfaceError) and stop_event.is_set(): + continue + _logger.exception("Bus.loop error, sleep and retry") + time.sleep(TIMEOUT) + +# Partially undo a2ed3d3d5bdb6025a1ba14ad557a115a86413e65 +# IMDispatch has a lazy start, so we could initialize it anyway +# And this avoids the Bus unavailable error messages +dispatch = ImDispatch() +stop_event = threading.Event() +CommonServer.on_stop(stop_event.set) diff --git a/models/bus_presence.py b/models/bus_presence.py new file mode 100644 index 0000000..f545b19 --- /dev/null +++ b/models/bus_presence.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +import datetime +import time + +from psycopg2 import OperationalError + +from odoo import api, fields, models +from odoo import tools +from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY +from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + +UPDATE_PRESENCE_DELAY = 60 +DISCONNECTION_TIMER = UPDATE_PRESENCE_DELAY + 5 +AWAY_TIMER = 1800 # 30 minutes + + +class BusPresence(models.Model): + """ User Presence + Its status is 'online', 'away' or 'offline'. This model should be a one2one, but is not + attached to res_users to avoid database concurrence errors. Since the 'update_presence' method is executed + at each poll, if the user have multiple opened tabs, concurrence errors can happend, but are 'muted-logged'. + """ + + _name = 'bus.presence' + _description = 'User Presence' + _log_access = False + + user_id = fields.Many2one('res.users', 'Users', ondelete='cascade') + last_poll = fields.Datetime('Last Poll', default=lambda self: fields.Datetime.now()) + last_presence = fields.Datetime('Last Presence', default=lambda self: fields.Datetime.now()) + status = fields.Selection([('online', 'Online'), ('away', 'Away'), ('offline', 'Offline')], 'IM Status', default='offline') + + def init(self): + self.env.cr.execute("CREATE UNIQUE INDEX IF NOT EXISTS bus_presence_user_unique ON %s (user_id) WHERE user_id IS NOT NULL" % self._table) + + @api.model + def update_presence(self, inactivity_period, identity_field, identity_value): + """ Updates the last_poll and last_presence of the current user + :param inactivity_period: duration in milliseconds + """ + # This method is called in method _poll() and cursor is closed right + # after; see bus/controllers/main.py. + try: + # Hide transaction serialization errors, which can be ignored, the presence update is not essential + # The errors are supposed from presence.write(...) call only + with tools.mute_logger('odoo.sql_db'): + self._update_presence(inactivity_period=inactivity_period, identity_field=identity_field, identity_value=identity_value) + # commit on success + self.env.cr.commit() + except OperationalError as e: + if e.pgcode in PG_CONCURRENCY_ERRORS_TO_RETRY: + # ignore concurrency error + return self.env.cr.rollback() + raise + + @api.model + def _update_presence(self, inactivity_period, identity_field, identity_value): + presence = self.search([(identity_field, '=', identity_value)], limit=1) + # compute last_presence timestamp + last_presence = datetime.datetime.now() - datetime.timedelta(milliseconds=inactivity_period) + values = { + 'last_poll': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + } + # update the presence or a create a new one + if not presence: # create a new presence for the user + values[identity_field] = identity_value + values['last_presence'] = last_presence + self.create(values) + else: # update the last_presence if necessary, and write values + if presence.last_presence < last_presence: + values['last_presence'] = last_presence + presence.write(values) + + @api.autovacuum + def _gc_bus_presence(self): + self.search([('user_id.active', '=', False)]).unlink() diff --git a/models/ir_model.py b/models/ir_model.py new file mode 100644 index 0000000..b673c6b --- /dev/null +++ b/models/ir_model.py @@ -0,0 +1,35 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class IrModel(models.Model): + _inherit = 'ir.model' + + def _get_model_definitions(self, model_names_to_fetch): + fields_by_model_names = {} + for model_name in model_names_to_fetch: + model = self.env[model_name] + # get fields, relational fields are kept only if the related model is in model_names_to_fetch + fields_data_by_fname = { + fname: field_data + for fname, field_data in model.fields_get( + attributes={ + 'name', 'type', 'relation', 'required', 'readonly', 'selection', + 'string', 'definition_record', 'definition_record_field', + }, + ).items() + if not field_data.get('relation') or field_data['relation'] in model_names_to_fetch + } + for fname, field_data in fields_data_by_fname.items(): + if fname in model._fields: + inverse_fields = [ + field for field in model.pool.field_inverses[model._fields[fname]] + if field.model_name in model_names_to_fetch + ] + if inverse_fields: + field_data['inverse_fname_by_model_name'] = {field.model_name: field.name for field in inverse_fields} + if field_data['type'] == 'many2one_reference': + field_data['model_name_ref_fname'] = model._fields[fname].model_field + fields_by_model_names[model_name] = fields_data_by_fname + return fields_by_model_names diff --git a/models/ir_websocket.py b/models/ir_websocket.py new file mode 100644 index 0000000..5e2f8fa --- /dev/null +++ b/models/ir_websocket.py @@ -0,0 +1,63 @@ +from odoo import models +from odoo.http import request, SessionExpiredException +from odoo.service import security +from ..models.bus import dispatch +from ..websocket import wsrequest + + +class IrWebsocket(models.AbstractModel): + _name = 'ir.websocket' + _description = 'websocket message handling' + + def _get_im_status(self, im_status_ids_by_model): + im_status = {} + if 'res.partner' in im_status_ids_by_model: + im_status['Persona'] = [{**p, 'type': "partner"} for p in self.env['res.partner'].with_context(active_test=False).search_read( + [('id', 'in', im_status_ids_by_model['res.partner'])], + ['im_status'] + )] + return im_status + + def _build_bus_channel_list(self, channels): + """ + Return the list of channels to subscribe to. Override this + method to add channels in addition to the ones the client + sent. + + :param channels: The channel list sent by the client. + """ + req = request or wsrequest + channels.append('broadcast') + if req.session.uid: + channels.append(self.env.user.partner_id) + return channels + + def _subscribe(self, data): + if not all(isinstance(c, str) for c in data['channels']): + raise ValueError("bus.Bus only string channels are allowed.") + last_known_notification_id = self.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0 + if data['last'] > last_known_notification_id: + data['last'] = 0 + channels = set(self._build_bus_channel_list(data['channels'])) + dispatch.subscribe(channels, data['last'], self.env.registry.db_name, wsrequest.ws) + + def _update_bus_presence(self, inactivity_period, im_status_ids_by_model): + if self.env.user and not self.env.user._is_public(): + self.env['bus.presence'].update_presence( + inactivity_period, + identity_field='user_id', + identity_value=self.env.uid + ) + im_status_notification = self._get_im_status(im_status_ids_by_model) + if im_status_notification: + self.env['bus.bus']._sendone(self.env.user.partner_id, 'mail.record/insert', im_status_notification) + + @classmethod + def _authenticate(cls): + if wsrequest.session.uid is not None: + if not security.check_session(wsrequest.session, wsrequest.env): + wsrequest.session.logout(keep_db=True) + raise SessionExpiredException() + else: + public_user = wsrequest.env.ref('base.public_user') + wsrequest.update_env(user=public_user.id) diff --git a/models/res_partner.py b/models/res_partner.py new file mode 100644 index 0000000..71f2f8f --- /dev/null +++ b/models/res_partner.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models +from odoo.addons.bus.models.bus_presence import AWAY_TIMER +from odoo.addons.bus.models.bus_presence import DISCONNECTION_TIMER + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + im_status = fields.Char('IM Status', compute='_compute_im_status') + + def _compute_im_status(self): + self.env.cr.execute(""" + SELECT + U.partner_id as id, + CASE WHEN max(B.last_poll) IS NULL THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away' + ELSE 'online' + END as status + FROM bus_presence B + RIGHT JOIN res_users U ON B.user_id = U.id + WHERE U.partner_id IN %s AND U.active = 't' + GROUP BY U.partner_id + """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids))) + res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall())) + for partner in self: + partner.im_status = res.get(partner.id, 'im_partner') # if not found, it is a partner, useful to avoid to refresh status in js diff --git a/models/res_users.py b/models/res_users.py new file mode 100644 index 0000000..8e40c1b --- /dev/null +++ b/models/res_users.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models +from odoo.addons.bus.models.bus_presence import AWAY_TIMER +from odoo.addons.bus.models.bus_presence import DISCONNECTION_TIMER + + +class ResUsers(models.Model): + + _inherit = "res.users" + + im_status = fields.Char('IM Status', compute='_compute_im_status') + + def _compute_im_status(self): + """ Compute the im_status of the users """ + self.env.cr.execute(""" + SELECT + user_id as id, + CASE WHEN age(now() AT TIME ZONE 'UTC', last_poll) > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', last_presence) > interval %s THEN 'away' + ELSE 'online' + END as status + FROM bus_presence + WHERE user_id IN %s + """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids))) + res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall())) + for user in self: + user.im_status = res.get(user.id, 'offline') diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..4a626a0 --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_bus_bus,bus.bus public,model_bus_bus,,0,0,0,0 +access_bus_presence,bus.presence,model_bus_presence,base.group_user,1,1,1,1 +access_bus_presence_portal,bus.presence,model_bus_presence,base.group_portal,1,1,1,1 diff --git a/static/src/bus_parameters_service.js b/static/src/bus_parameters_service.js new file mode 100644 index 0000000..eb803d4 --- /dev/null +++ b/static/src/bus_parameters_service.js @@ -0,0 +1,13 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; + +export const busParametersService = { + start() { + return { + serverURL: window.origin, + }; + }, +}; + +registry.category("services").add("bus.parameters", busParametersService); diff --git a/static/src/im_status_service.js b/static/src/im_status_service.js new file mode 100644 index 0000000..24041f5 --- /dev/null +++ b/static/src/im_status_service.js @@ -0,0 +1,77 @@ +/** @odoo-module **/ + +import { browser } from "@web/core/browser/browser"; +import { registry } from "@web/core/registry"; +import { timings } from "@bus/misc"; + +export const UPDATE_BUS_PRESENCE_DELAY = 60000; +/** + * This service updates periodically the user presence in order for the + * im_status to be up to date. + * + * In order to receive bus notifications related to im_status, one must + * register model/ids to monitor to this service. + */ +export const imStatusService = { + dependencies: ["bus_service", "multi_tab", "presence"], + + start(env, { bus_service, multi_tab, presence }) { + const imStatusModelToIds = {}; + let updateBusPresenceTimeout; + const throttledUpdateBusPresence = timings.throttle(function updateBusPresence() { + clearTimeout(updateBusPresenceTimeout); + if (!multi_tab.isOnMainTab()) { + return; + } + const now = new Date().getTime(); + bus_service.send("update_presence", { + inactivity_period: now - presence.getLastPresence(), + im_status_ids_by_model: { ...imStatusModelToIds }, + }); + updateBusPresenceTimeout = browser.setTimeout( + throttledUpdateBusPresence, + UPDATE_BUS_PRESENCE_DELAY + ); + }, UPDATE_BUS_PRESENCE_DELAY); + + bus_service.addEventListener("connect", () => { + // wait for im_status model/ids to be registered before starting. + browser.setTimeout(throttledUpdateBusPresence, 250); + }); + multi_tab.bus.addEventListener("become_main_tab", throttledUpdateBusPresence); + bus_service.addEventListener("reconnect", throttledUpdateBusPresence); + multi_tab.bus.addEventListener("no_longer_main_tab", () => + clearTimeout(updateBusPresenceTimeout) + ); + bus_service.addEventListener("disconnect", () => clearTimeout(updateBusPresenceTimeout)); + + return { + /** + * Register model/ids whose im_status should be monitored. + * Notification related to the im_status are then sent + * through the bus. Overwrite registration if already + * present. + * + * @param {string} model model related to the given ids. + * @param {Number[]} ids ids whose im_status should be + * monitored. + */ + registerToImStatus(model, ids) { + if (!ids.length) { + return this.unregisterFromImStatus(model); + } + imStatusModelToIds[model] = ids; + }, + /** + * Unregister model from im_status notifications. + * + * @param {string} model model to unregister. + */ + unregisterFromImStatus(model) { + delete imStatusModelToIds[model]; + }, + }; + }, +}; + +registry.category("services").add("im_status", imStatusService); diff --git a/static/src/misc.js b/static/src/misc.js new file mode 100644 index 0000000..6b4461e --- /dev/null +++ b/static/src/misc.js @@ -0,0 +1,65 @@ +/** @odoo-module */ + +import { browser } from "@web/core/browser/browser"; + +/** + * Returns a function, that, when invoked, will only be triggered at most once + * during a given window of time. Normally, the throttled function will run + * as much as it can, without ever going more than once per `wait` duration; + * but if you'd like to disable the execution on the leading edge, pass + * `{leading: false}`. To disable execution on the trailing edge, ditto. + * + * credit to `underscore.js` + */ +function throttle(func, wait, options) { + let timeout, context, args, result; + let previous = 0; + if (!options) { + options = {}; + } + + const later = function () { + previous = options.leading === false ? 0 : Date.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) { + context = args = null; + } + }; + + const throttled = function () { + const _now = Date.now(); + if (!previous && options.leading === false) { + previous = _now; + } + const remaining = wait - (_now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + browser.clearTimeout(timeout); + timeout = null; + } + previous = _now; + result = func.apply(context, args); + if (!timeout) { + context = args = null; + } + } else if (!timeout && options.trailing !== false) { + timeout = browser.setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function () { + browser.clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; +} + +export const timings = { + throttle, +}; diff --git a/static/src/multi_tab_service.js b/static/src/multi_tab_service.js new file mode 100644 index 0000000..b9ee0b9 --- /dev/null +++ b/static/src/multi_tab_service.js @@ -0,0 +1,223 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { browser } from "@web/core/browser/browser"; +import { EventBus } from "@odoo/owl"; + +let multiTabId = 0; +/** + * This service uses a Master/Slaves with Leader Election architecture in + * order to keep track of the main tab. Tabs are synchronized thanks to the + * localStorage. + * + * localStorage used keys are: + * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.lastPresenceByTab: + * mapping of tab ids to their last recorded presence. + * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.main : id of the current + * main tab. + * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.heartbeat : last main tab + * heartbeat time. + * + * trigger: + * - become_main_tab : when this tab became the main. + * - no_longer_main_tab : when this tab is no longer the main. + * - shared_value_updated: when one of the shared values changes. + */ +export const multiTabService = { + start() { + const bus = new EventBus(); + + // CONSTANTS + const TAB_HEARTBEAT_PERIOD = 10000; // 10 seconds + const MAIN_TAB_HEARTBEAT_PERIOD = 1500; // 1.5 seconds + const HEARTBEAT_OUT_OF_DATE_PERIOD = 5000; // 5 seconds + const HEARTBEAT_KILL_OLD_PERIOD = 15000; // 15 seconds + // Keys that should not trigger the `shared_value_updated` event. + const PRIVATE_LOCAL_STORAGE_KEYS = ["main", "heartbeat"]; + + // PROPERTIES + let _isOnMainTab = false; + let lastHeartbeat = 0; + let heartbeatTimeout; + const sanitizedOrigin = location.origin.replace(/:\/{0,2}/g, "_"); + const localStoragePrefix = `${this.name}.${sanitizedOrigin}.`; + const now = new Date().getTime(); + const tabId = `${this.name}${multiTabId++}:${now}`; + + function generateLocalStorageKey(baseKey) { + return localStoragePrefix + baseKey; + } + + function getItemFromStorage(key, defaultValue) { + const item = browser.localStorage.getItem(generateLocalStorageKey(key)); + try { + return item ? JSON.parse(item) : defaultValue; + } catch { + return item; + } + } + + function setItemInStorage(key, value) { + browser.localStorage.setItem(generateLocalStorageKey(key), JSON.stringify(value)); + } + + function startElection() { + if (_isOnMainTab) { + return; + } + // Check who's next. + const now = new Date().getTime(); + const lastPresenceByTab = getItemFromStorage("lastPresenceByTab", {}); + const heartbeatKillOld = now - HEARTBEAT_KILL_OLD_PERIOD; + let newMain; + for (const [tab, lastPresence] of Object.entries(lastPresenceByTab)) { + // Check for dead tabs. + if (lastPresence < heartbeatKillOld) { + continue; + } + newMain = tab; + break; + } + if (newMain === tabId) { + // We're next in queue. Electing as main. + lastHeartbeat = now; + setItemInStorage("heartbeat", lastHeartbeat); + setItemInStorage("main", true); + _isOnMainTab = true; + bus.trigger("become_main_tab"); + // Removing main peer from queue. + delete lastPresenceByTab[newMain]; + setItemInStorage("lastPresenceByTab", lastPresenceByTab); + } + } + + function heartbeat() { + const now = new Date().getTime(); + let heartbeatValue = getItemFromStorage("heartbeat", 0); + const lastPresenceByTab = getItemFromStorage("lastPresenceByTab", {}); + if (heartbeatValue + HEARTBEAT_OUT_OF_DATE_PERIOD < now) { + // Heartbeat is out of date. Electing new main. + startElection(); + heartbeatValue = getItemFromStorage("heartbeat", 0); + } + if (_isOnMainTab) { + // Walk through all tabs and kill old ones. + const cleanedTabs = {}; + for (const [tabId, lastPresence] of Object.entries(lastPresenceByTab)) { + if (lastPresence + HEARTBEAT_KILL_OLD_PERIOD > now) { + cleanedTabs[tabId] = lastPresence; + } + } + if (heartbeatValue !== lastHeartbeat) { + // Someone else is also main... + // It should not happen, except in some race condition situation. + _isOnMainTab = false; + lastHeartbeat = 0; + lastPresenceByTab[tabId] = now; + setItemInStorage("lastPresenceByTab", lastPresenceByTab); + bus.trigger("no_longer_main_tab"); + } else { + lastHeartbeat = now; + setItemInStorage("heartbeat", now); + setItemInStorage("lastPresenceByTab", cleanedTabs); + } + } else { + // Update own heartbeat. + lastPresenceByTab[tabId] = now; + setItemInStorage("lastPresenceByTab", lastPresenceByTab); + } + const hbPeriod = _isOnMainTab ? MAIN_TAB_HEARTBEAT_PERIOD : TAB_HEARTBEAT_PERIOD; + heartbeatTimeout = browser.setTimeout(heartbeat, hbPeriod); + } + + function onStorage({ key, newValue }) { + if (key === generateLocalStorageKey("main") && !newValue) { + // Main was unloaded. + startElection(); + } + if (PRIVATE_LOCAL_STORAGE_KEYS.includes(key)) { + return; + } + if (key && key.includes(localStoragePrefix)) { + // Only trigger the shared_value_updated event if the key is + // related to this service/origin. + const baseKey = key.replace(localStoragePrefix, ""); + bus.trigger("shared_value_updated", { key: baseKey, newValue }); + } + } + + function onPagehide() { + clearTimeout(heartbeatTimeout); + const lastPresenceByTab = getItemFromStorage("lastPresenceByTab", {}); + delete lastPresenceByTab[tabId]; + setItemInStorage("lastPresenceByTab", lastPresenceByTab); + + // Unload main. + if (_isOnMainTab) { + _isOnMainTab = false; + bus.trigger("no_longer_main_tab"); + browser.localStorage.removeItem(generateLocalStorageKey("main")); + } + } + + browser.addEventListener("pagehide", onPagehide); + browser.addEventListener("storage", onStorage); + + // REGISTER THIS TAB + const lastPresenceByTab = getItemFromStorage("lastPresenceByTab", {}); + lastPresenceByTab[tabId] = now; + setItemInStorage("lastPresenceByTab", lastPresenceByTab); + + if (!getItemFromStorage("main")) { + startElection(); + } + heartbeat(); + + return { + bus, + get currentTabId() { + return tabId; + }, + /** + * Determine whether or not this tab is the main one. + * + * @returns {boolean} + */ + isOnMainTab() { + return _isOnMainTab; + }, + /** + * Get value shared between all the tabs. + * + * @param {string} key + * @param {any} defaultValue Value to be returned if this + * key does not exist. + */ + getSharedValue(key, defaultValue) { + return getItemFromStorage(key, defaultValue); + }, + /** + * Set value shared between all the tabs. + * + * @param {string} key + * @param {any} value + */ + setSharedValue(key, value) { + if (value === undefined) { + return this.removeSharedValue(key); + } + setItemInStorage(key, value); + }, + /** + * Remove value shared between all the tabs. + * + * @param {string} key + */ + removeSharedValue(key) { + browser.localStorage.removeItem(generateLocalStorageKey(key)); + }, + }; + }, +}; + +registry.category("services").add("multi_tab", multiTabService); diff --git a/static/src/services/assets_watchdog_service.js b/static/src/services/assets_watchdog_service.js new file mode 100644 index 0000000..8850006 --- /dev/null +++ b/static/src/services/assets_watchdog_service.js @@ -0,0 +1,67 @@ +/** @odoo-module **/ + +import { _t } from "@web/core/l10n/translation"; +import { browser } from "@web/core/browser/browser"; +import { registry } from "@web/core/registry"; +import { session } from "@web/session"; + +export const assetsWatchdogService = { + dependencies: ["bus_service", "notification"], + + start(env, { bus_service, notification }) { + let isNotificationDisplayed = false; + let bundleNotifTimerID = null; + + bus_service.subscribe("bundle_changed", ({ server_version }) => { + if (server_version !== session.server_version) { + displayBundleChangedNotification(); + } + }); + bus_service.start(); + + /** + * Displays one notification on user's screen when assets have changed + */ + function displayBundleChangedNotification() { + if (!isNotificationDisplayed) { + // Wrap the notification inside a delay. + // The server may be overwhelmed with recomputing assets + // We wait until things settle down + browser.clearTimeout(bundleNotifTimerID); + bundleNotifTimerID = browser.setTimeout(() => { + notification.add(_t("The page appears to be out of date."), { + title: _t("Refresh"), + type: "warning", + sticky: true, + buttons: [ + { + name: _t("Refresh"), + primary: true, + onClick: () => { + browser.location.reload(); + }, + }, + ], + onClose: () => { + isNotificationDisplayed = false; + }, + }); + isNotificationDisplayed = true; + }, getBundleNotificationDelay()); + } + } + + /** + * Computes a random delay to avoid hammering the server + * when bundles change with all the users reloading + * at the same time + * + * @return {number} delay in milliseconds + */ + function getBundleNotificationDelay() { + return 10000 + Math.floor(Math.random() * 50) * 1000; + } + }, +}; + +registry.category("services").add("assetsWatchdog", assetsWatchdogService); diff --git a/static/src/services/bus_service.js b/static/src/services/bus_service.js new file mode 100644 index 0000000..2e2fa9d --- /dev/null +++ b/static/src/services/bus_service.js @@ -0,0 +1,208 @@ +/** @odoo-module **/ + +import { browser } from "@web/core/browser/browser"; +import { Deferred } from "@web/core/utils/concurrency"; +import { registry } from "@web/core/registry"; +import { session } from "@web/session"; +import { isIosApp } from "@web/core/browser/feature_detection"; +import { WORKER_VERSION } from "@bus/workers/websocket_worker"; +import { EventBus } from "@odoo/owl"; + +/** + * Communicate with a SharedWorker in order to provide a single websocket + * connection shared across multiple tabs. + * + * @emits connect + * @emits disconnect + * @emits reconnect + * @emits reconnecting + * @emits notification + */ +export const busService = { + dependencies: ["bus.parameters", "localization", "multi_tab"], + async: true, + + start(env, { multi_tab: multiTab, "bus.parameters": params }) { + const bus = new EventBus(); + const notificationBus = new EventBus(); + let worker; + let isActive = false; + let isInitialized = false; + let isUsingSharedWorker = browser.SharedWorker && !isIosApp(); + const startedAt = luxon.DateTime.now().set({ milliseconds: 0 }); + const connectionInitializedDeferred = new Deferred(); + + /** + * Send a message to the worker. + * + * @param {WorkerAction} action Action to be + * executed by the worker. + * @param {Object|undefined} data Data required for the action to be + * executed. + */ + function send(action, data) { + if (!worker) { + return; + } + const message = { action, data }; + if (isUsingSharedWorker) { + worker.port.postMessage(message); + } else { + worker.postMessage(message); + } + } + + /** + * Handle messages received from the shared worker and fires an + * event according to the message type. + * + * @param {MessageEvent} messageEv + * @param {{type: WorkerEvent, data: any}[]} messageEv.data + */ + function handleMessage(messageEv) { + const { type } = messageEv.data; + let { data } = messageEv.data; + if (type === "notification") { + data.forEach((d) => (d.message.id = d.id)); // put notification id in notif message + multiTab.setSharedValue("last_notification_id", data[data.length - 1].id); + data = data.map((notification) => notification.message); + for (const { type, payload } of data) { + notificationBus.trigger(type, payload); + } + } else if (type === "initialized") { + isInitialized = true; + connectionInitializedDeferred.resolve(); + return; + } + bus.trigger(type, data); + } + + /** + * Initialize the connection to the worker by sending it usefull + * initial informations (last notification id, debug mode, + * ...). + */ + function initializeWorkerConnection() { + // User_id has different values according to its origin: + // - frontend: number or false, + // - backend: array with only one number + // - guest page: array containing null or number + // - public pages: undefined + // Let's format it in order to ease its usage: + // - number if user is logged, false otherwise, keep + // undefined to indicate session_info is not available. + let uid = Array.isArray(session.user_id) ? session.user_id[0] : session.user_id; + if (!uid && uid !== undefined) { + uid = false; + } + send("initialize_connection", { + websocketURL: `${params.serverURL.replace("http", "ws")}/websocket`, + db: session.db, + debug: odoo.debug, + lastNotificationId: multiTab.getSharedValue("last_notification_id", 0), + uid, + startTs: startedAt.valueOf(), + }); + } + + /** + * Start the "bus_service" worker. + */ + function startWorker() { + let workerURL = `${params.serverURL}/bus/websocket_worker_bundle?v=${WORKER_VERSION}`; + if (params.serverURL !== window.origin) { + // Bus service is loaded from a different origin than the bundle + // URL. The Worker expects an URL from this origin, give it a base64 + // URL that will then load the bundle via "importScripts" which + // allows cross origin. + const source = `importScripts("${workerURL}");`; + workerURL = "data:application/javascript;base64," + window.btoa(source); + } + const workerClass = isUsingSharedWorker ? browser.SharedWorker : browser.Worker; + worker = new workerClass(workerURL, { + name: isUsingSharedWorker + ? "odoo:websocket_shared_worker" + : "odoo:websocket_worker", + }); + worker.addEventListener("error", (e) => { + if (!isInitialized && workerClass === browser.SharedWorker) { + console.warn( + 'Error while loading "bus_service" SharedWorker, fallback on Worker.' + ); + isUsingSharedWorker = false; + startWorker(); + } else if (!isInitialized) { + isInitialized = true; + connectionInitializedDeferred.resolve(); + console.warn("Bus service failed to initialized."); + } + }); + if (isUsingSharedWorker) { + worker.port.start(); + worker.port.addEventListener("message", handleMessage); + } else { + worker.addEventListener("message", handleMessage); + } + initializeWorkerConnection(); + } + browser.addEventListener("pagehide", ({ persisted }) => { + if (!persisted) { + // Page is gonna be unloaded, disconnect this client + // from the worker. + send("leave"); + } + }); + browser.addEventListener("online", () => { + if (isActive) { + send("start"); + } + }); + browser.addEventListener("offline", () => send("stop")); + + return { + addEventListener: bus.addEventListener.bind(bus), + addChannel: async (channel) => { + if (!worker) { + startWorker(); + await connectionInitializedDeferred; + } + send("add_channel", channel); + send("start"); + isActive = true; + }, + deleteChannel: (channel) => send("delete_channel", channel), + forceUpdateChannels: () => send("force_update_channels"), + trigger: bus.trigger.bind(bus), + removeEventListener: bus.removeEventListener.bind(bus), + send: (eventName, data) => send("send", { event_name: eventName, data }), + start: async () => { + if (!worker) { + startWorker(); + await connectionInitializedDeferred; + } + send("start"); + isActive = true; + }, + stop: () => { + send("leave"); + isActive = false; + }, + get isActive() { + return isActive; + }, + /** + * Subscribe to a single notification type. + * + * @param {string} notificationType + * @param {function} callback + */ + subscribe(notificationType, callback) { + notificationBus.addEventListener(notificationType, ({ detail }) => + callback(detail) + ); + }, + startedAt, + }; + }, +}; +registry.category("services").add("bus_service", busService); diff --git a/static/src/services/presence_service.js b/static/src/services/presence_service.js new file mode 100644 index 0000000..5d54da7 --- /dev/null +++ b/static/src/services/presence_service.js @@ -0,0 +1,61 @@ +/** @odoo-module **/ + +import { browser } from "@web/core/browser/browser"; +import { registry } from "@web/core/registry"; + +export const presenceService = { + start(env) { + const LOCAL_STORAGE_PREFIX = "presence"; + + let isOdooFocused = true; + let lastPresenceTime = + browser.localStorage.getItem(`${LOCAL_STORAGE_PREFIX}.lastPresence`) || + new Date().getTime(); + + function onPresence() { + lastPresenceTime = new Date().getTime(); + browser.localStorage.setItem(`${LOCAL_STORAGE_PREFIX}.lastPresence`, lastPresenceTime); + } + + function onFocusChange(isFocused) { + try { + isFocused = parent.document.hasFocus(); + } catch { + // noop + } + isOdooFocused = isFocused; + browser.localStorage.setItem(`${LOCAL_STORAGE_PREFIX}.focus`, isOdooFocused); + if (isOdooFocused) { + lastPresenceTime = new Date().getTime(); + env.bus.trigger("window_focus", isOdooFocused); + } + } + + function onStorage({ key, newValue }) { + if (key === `${LOCAL_STORAGE_PREFIX}.focus`) { + isOdooFocused = JSON.parse(newValue); + env.bus.trigger("window_focus", newValue); + } + if (key === `${LOCAL_STORAGE_PREFIX}.lastPresence`) { + lastPresenceTime = JSON.parse(newValue); + } + } + browser.addEventListener("storage", onStorage); + browser.addEventListener("focus", () => onFocusChange(true)); + browser.addEventListener("blur", () => onFocusChange(false)); + browser.addEventListener("pagehide", () => onFocusChange(false)); + browser.addEventListener("click", onPresence); + browser.addEventListener("keydown", onPresence); + + return { + getLastPresence() { + return lastPresenceTime; + }, + isOdooFocused() { + return isOdooFocused; + }, + }; + }, +}; + +registry.category("services").add("presence", presenceService); diff --git a/static/src/simple_notification_service.js b/static/src/simple_notification_service.js new file mode 100644 index 0000000..7d3ffe7 --- /dev/null +++ b/static/src/simple_notification_service.js @@ -0,0 +1,15 @@ +/* @odoo-module */ + +import { registry } from "@web/core/registry"; + +export const simpleNotificationService = { + dependencies: ["bus_service", "notification"], + start(env, { bus_service, notification: notificationService }) { + bus_service.subscribe("simple_notification", ({ message, sticky, title, type }) => { + notificationService.add(message, { sticky, title, type }); + }); + bus_service.start(); + }, +}; + +registry.category("services").add("simple_notification", simpleNotificationService); diff --git a/static/src/workers/websocket_worker.js b/static/src/workers/websocket_worker.js new file mode 100644 index 0000000..b13af95 --- /dev/null +++ b/static/src/workers/websocket_worker.js @@ -0,0 +1,474 @@ +/** @odoo-module **/ + +import { debounce } from "@bus/workers/websocket_worker_utils"; + +/** + * Type of events that can be sent from the worker to its clients. + * + * @typedef { 'connect' | 'reconnect' | 'disconnect' | 'reconnecting' | 'notification' | 'initialized' } WorkerEvent + */ + +/** + * Type of action that can be sent from the client to the worker. + * + * @typedef {'add_channel' | 'delete_channel' | 'force_update_channels' | 'initialize_connection' | 'send' | 'leave' | 'stop' | 'start'} WorkerAction + */ + +export const WEBSOCKET_CLOSE_CODES = Object.freeze({ + CLEAN: 1000, + GOING_AWAY: 1001, + PROTOCOL_ERROR: 1002, + INCORRECT_DATA: 1003, + ABNORMAL_CLOSURE: 1006, + INCONSISTENT_DATA: 1007, + MESSAGE_VIOLATING_POLICY: 1008, + MESSAGE_TOO_BIG: 1009, + EXTENSION_NEGOTIATION_FAILED: 1010, + SERVER_ERROR: 1011, + RESTART: 1012, + TRY_LATER: 1013, + BAD_GATEWAY: 1014, + SESSION_EXPIRED: 4001, + KEEP_ALIVE_TIMEOUT: 4002, + RECONNECTING: 4003, +}); +// Should be incremented on every worker update in order to force +// update of the worker in browser cache. +export const WORKER_VERSION = "1.0.7"; +const MAXIMUM_RECONNECT_DELAY = 60000; + +/** + * This class regroups the logic necessary in order for the + * SharedWorker/Worker to work. Indeed, Safari and some minor browsers + * do not support SharedWorker. In order to solve this issue, a Worker + * is used in this case. The logic is almost the same than the one used + * for SharedWorker and this class implements it. + */ +export class WebsocketWorker { + INITIAL_RECONNECT_DELAY = 1000; + RECONNECT_JITTER = 1000; + + constructor() { + // Timestamp of start of most recent bus service sender + this.newestStartTs = undefined; + this.websocketURL = ""; + this.currentUID = null; + this.currentDB = null; + this.isWaitingForNewUID = true; + this.channelsByClient = new Map(); + this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY; + this.connectTimeout = null; + this.debugModeByClient = new Map(); + this.isDebug = false; + this.isReconnecting = false; + this.lastChannelSubscription = null; + this.lastNotificationId = 0; + this.messageWaitQueue = []; + this._forceUpdateChannels = debounce(this._forceUpdateChannels, 300); + this._updateChannels = debounce(this._updateChannels, 0); + + this._onWebsocketClose = this._onWebsocketClose.bind(this); + this._onWebsocketError = this._onWebsocketError.bind(this); + this._onWebsocketMessage = this._onWebsocketMessage.bind(this); + this._onWebsocketOpen = this._onWebsocketOpen.bind(this); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Send the message to all the clients that are connected to the + * worker. + * + * @param {WorkerEvent} type Event to broadcast to connected + * clients. + * @param {Object} data + */ + broadcast(type, data) { + for (const client of this.channelsByClient.keys()) { + client.postMessage({ type, data }); + } + } + + /** + * Register a client handled by this worker. + * + * @param {MessagePort} messagePort + */ + registerClient(messagePort) { + messagePort.onmessage = (ev) => { + this._onClientMessage(messagePort, ev.data); + }; + this.channelsByClient.set(messagePort, []); + } + + /** + * Send message to the given client. + * + * @param {number} client + * @param {WorkerEvent} type + * @param {Object} data + */ + sendToClient(client, type, data) { + client.postMessage({ type, data }); + } + + //-------------------------------------------------------------------------- + // PRIVATE + //-------------------------------------------------------------------------- + + /** + * Called when a message is posted to the worker by a client (i.e. a + * MessagePort connected to this worker). + * + * @param {MessagePort} client + * @param {Object} message + * @param {WorkerAction} [message.action] + * Action to execute. + * @param {Object|undefined} [message.data] Data required by the + * action. + */ + _onClientMessage(client, { action, data }) { + switch (action) { + case "send": + return this._sendToServer(data); + case "start": + return this._start(); + case "stop": + return this._stop(); + case "leave": + return this._unregisterClient(client); + case "add_channel": + return this._addChannel(client, data); + case "delete_channel": + return this._deleteChannel(client, data); + case "force_update_channels": + return this._forceUpdateChannels(); + case "initialize_connection": + return this._initializeConnection(client, data); + } + } + + /** + * Add a channel for the given client. If this channel is not yet + * known, update the subscription on the server. + * + * @param {MessagePort} client + * @param {string} channel + */ + _addChannel(client, channel) { + const clientChannels = this.channelsByClient.get(client); + if (!clientChannels.includes(channel)) { + clientChannels.push(channel); + this.channelsByClient.set(client, clientChannels); + this._updateChannels(); + } + } + + /** + * Remove a channel for the given client. If this channel is not + * used anymore, update the subscription on the server. + * + * @param {MessagePort} client + * @param {string} channel + */ + _deleteChannel(client, channel) { + const clientChannels = this.channelsByClient.get(client); + if (!clientChannels) { + return; + } + const channelIndex = clientChannels.indexOf(channel); + if (channelIndex !== -1) { + clientChannels.splice(channelIndex, 1); + this._updateChannels(); + } + } + + /** + * Update the channels on the server side even if the channels on + * the client side are the same than the last time we subscribed. + */ + _forceUpdateChannels() { + this._updateChannels({ force: true }); + } + + /** + * Remove the given client from this worker client list as well as + * its channels. If some of its channels are not used anymore, + * update the subscription on the server. + * + * @param {MessagePort} client + */ + _unregisterClient(client) { + this.channelsByClient.delete(client); + this.debugModeByClient.delete(client); + this.isDebug = Object.values(this.debugModeByClient).some( + (debugValue) => debugValue !== "" + ); + this._updateChannels(); + } + + /** + * Initialize a client connection to this worker. + * + * @param {Object} param0 + * @param {string} [param0.db] Database name. + * @param {String} [param0.debug] Current debugging mode for the + * given client. + * @param {Number} [param0.lastNotificationId] Last notification id + * known by the client. + * @param {String} [param0.websocketURL] URL of the websocket endpoint. + * @param {Number|false|undefined} [param0.uid] Current user id + * - Number: user is logged whether on the frontend/backend. + * - false: user is not logged. + * - undefined: not available (e.g. livechat support page) + * @param {Number} param0.startTs Timestamp of start of bus service sender. + */ + _initializeConnection(client, { db, debug, lastNotificationId, uid, websocketURL, startTs }) { + if (this.newestStartTs && this.newestStartTs > startTs) { + this.debugModeByClient[client] = debug; + this.isDebug = Object.values(this.debugModeByClient).some( + (debugValue) => debugValue !== "" + ); + this.sendToClient(client, "initialized"); + return; + } + this.newestStartTs = startTs; + this.websocketURL = websocketURL; + this.lastNotificationId = lastNotificationId; + this.debugModeByClient[client] = debug; + this.isDebug = Object.values(this.debugModeByClient).some( + (debugValue) => debugValue !== "" + ); + const isCurrentUserKnown = uid !== undefined; + if (this.isWaitingForNewUID && isCurrentUserKnown) { + this.isWaitingForNewUID = false; + this.currentUID = uid; + } + if ((this.currentUID !== uid && isCurrentUserKnown) || this.currentDB !== db) { + this.currentUID = uid; + this.currentDB = db; + if (this.websocket) { + this.websocket.close(WEBSOCKET_CLOSE_CODES.CLEAN); + } + this.channelsByClient.forEach((_, key) => this.channelsByClient.set(key, [])); + } + this.sendToClient(client, "initialized"); + } + + /** + * Determine whether or not the websocket associated to this worker + * is connected. + * + * @returns {boolean} + */ + _isWebsocketConnected() { + return this.websocket && this.websocket.readyState === 1; + } + + /** + * Determine whether or not the websocket associated to this worker + * is connecting. + * + * @returns {boolean} + */ + _isWebsocketConnecting() { + return this.websocket && this.websocket.readyState === 0; + } + + /** + * Determine whether or not the websocket associated to this worker + * is in the closing state. + * + * @returns {boolean} + */ + _isWebsocketClosing() { + return this.websocket && this.websocket.readyState === 2; + } + + /** + * Triggered when a connection is closed. If closure was not clean , + * try to reconnect after indicating to the clients that the + * connection was closed. + * + * @param {CloseEvent} ev + * @param {number} code close code indicating why the connection + * was closed. + * @param {string} reason reason indicating why the connection was + * closed. + */ + _onWebsocketClose({ code, reason }) { + if (this.isDebug) { + console.debug( + `%c${new Date().toLocaleString()} - [onClose]`, + "color: #c6e; font-weight: bold;", + code, + reason + ); + } + this.lastChannelSubscription = null; + if (this.isReconnecting) { + // Connection was not established but the close event was + // triggered anyway. Let the onWebsocketError method handle + // this case. + return; + } + this.broadcast("disconnect", { code, reason }); + if (code === WEBSOCKET_CLOSE_CODES.CLEAN) { + // WebSocket was closed on purpose, do not try to reconnect. + return; + } + // WebSocket was not closed cleanly, let's try to reconnect. + this.broadcast("reconnecting", { closeCode: code }); + this.isReconnecting = true; + if (code === WEBSOCKET_CLOSE_CODES.KEEP_ALIVE_TIMEOUT) { + // Don't wait to reconnect on keep alive timeout. + this.connectRetryDelay = 0; + } + if (code === WEBSOCKET_CLOSE_CODES.SESSION_EXPIRED) { + this.isWaitingForNewUID = true; + } + this._retryConnectionWithDelay(); + } + + /** + * Triggered when a connection failed or failed to established. + */ + _onWebsocketError() { + if (this.isDebug) { + console.debug( + `%c${new Date().toLocaleString()} - [onError]`, + "color: #c6e; font-weight: bold;" + ); + } + this._retryConnectionWithDelay(); + } + + /** + * Handle data received from the bus. + * + * @param {MessageEvent} messageEv + */ + _onWebsocketMessage(messageEv) { + const notifications = JSON.parse(messageEv.data); + if (this.isDebug) { + console.debug( + `%c${new Date().toLocaleString()} - [onMessage]`, + "color: #c6e; font-weight: bold;", + notifications + ); + } + this.lastNotificationId = notifications[notifications.length - 1].id; + this.broadcast("notification", notifications); + } + + /** + * Triggered on websocket open. Send message that were waiting for + * the connection to open. + */ + _onWebsocketOpen() { + if (this.isDebug) { + console.debug( + `%c${new Date().toLocaleString()} - [onOpen]`, + "color: #c6e; font-weight: bold;" + ); + } + this._updateChannels(); + this.messageWaitQueue.forEach((msg) => this.websocket.send(msg)); + this.messageWaitQueue = []; + this.broadcast(this.isReconnecting ? "reconnect" : "connect"); + this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY; + this.connectTimeout = null; + this.isReconnecting = false; + } + + /** + * Try to reconnect to the server, an exponential back off is + * applied to the reconnect attempts. + */ + _retryConnectionWithDelay() { + this.connectRetryDelay = + Math.min(this.connectRetryDelay * 1.5, MAXIMUM_RECONNECT_DELAY) + + this.RECONNECT_JITTER * Math.random(); + this.connectTimeout = setTimeout(this._start.bind(this), this.connectRetryDelay); + } + + /** + * Send a message to the server through the websocket connection. + * If the websocket is not open, enqueue the message and send it + * upon the next reconnection. + * + * @param {{event_name: string, data: any }} message Message to send to the server. + */ + _sendToServer(message) { + const payload = JSON.stringify(message); + if (!this._isWebsocketConnected()) { + this.messageWaitQueue.push(payload); + } else { + this.websocket.send(payload); + } + } + + /** + * Start the worker by opening a websocket connection. + */ + _start() { + if (this._isWebsocketConnected() || this._isWebsocketConnecting()) { + return; + } + if (this.websocket) { + this.websocket.removeEventListener("open", this._onWebsocketOpen); + this.websocket.removeEventListener("message", this._onWebsocketMessage); + this.websocket.removeEventListener("error", this._onWebsocketError); + this.websocket.removeEventListener("close", this._onWebsocketClose); + } + if (this._isWebsocketClosing()) { + // close event was not triggered and will never be, broadcast the + // disconnect event for consistency sake. + this.lastChannelSubscription = null; + this.broadcast("disconnect", { code: WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE }); + } + this.websocket = new WebSocket(this.websocketURL); + this.websocket.addEventListener("open", this._onWebsocketOpen); + this.websocket.addEventListener("error", this._onWebsocketError); + this.websocket.addEventListener("message", this._onWebsocketMessage); + this.websocket.addEventListener("close", this._onWebsocketClose); + } + + /** + * Stop the worker. + */ + _stop() { + clearTimeout(this.connectTimeout); + this.connectRetryDelay = this.INITIAL_RECONNECT_DELAY; + this.isReconnecting = false; + this.lastChannelSubscription = null; + if (this.websocket) { + this.websocket.close(); + } + } + + /** + * Update the channel subscription on the server. Ignore if the channels + * did not change since the last subscription. + * + * @param {boolean} force Whether or not we should update the subscription + * event if the channels haven't change since last subscription. + */ + _updateChannels({ force = false } = {}) { + const allTabsChannels = [ + ...new Set([].concat.apply([], [...this.channelsByClient.values()])), + ].sort(); + const allTabsChannelsString = JSON.stringify(allTabsChannels); + const shouldUpdateChannelSubscription = + allTabsChannelsString !== this.lastChannelSubscription; + if (force || shouldUpdateChannelSubscription) { + this.lastChannelSubscription = allTabsChannelsString; + this._sendToServer({ + event_name: "subscribe", + data: { channels: allTabsChannels, last: this.lastNotificationId }, + }); + } + } +} diff --git a/static/src/workers/websocket_worker_script.js b/static/src/workers/websocket_worker_script.js new file mode 100644 index 0000000..a23c54c --- /dev/null +++ b/static/src/workers/websocket_worker_script.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ +/* eslint-env worker */ +/* eslint-disable no-restricted-globals */ + +import { WebsocketWorker } from "./websocket_worker"; + +(function () { + const websocketWorker = new WebsocketWorker(); + + if (self.name.includes("shared")) { + // The script is running in a shared worker: let's register every + // tab connection to the worker in order to relay notifications + // coming from the websocket. + onconnect = function (ev) { + const currentClient = ev.ports[0]; + websocketWorker.registerClient(currentClient); + }; + } else { + // The script is running in a simple web worker. + websocketWorker.registerClient(self); + } +})(); diff --git a/static/src/workers/websocket_worker_utils.js b/static/src/workers/websocket_worker_utils.js new file mode 100644 index 0000000..01131c7 --- /dev/null +++ b/static/src/workers/websocket_worker_utils.js @@ -0,0 +1,29 @@ +/** @odoo-module **/ + +/** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. If `immediate` is passed, trigger the function on the + * leading edge, instead of the trailing. + * + * Inspired by https://davidwalsh.name/javascript-debounce-function + */ +export function debounce(func, wait, immediate) { + let timeout; + return function () { + const context = this; + const args = arguments; + function later() { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + } + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + func.apply(context, args); + } + }; +} diff --git a/static/tests/assets_watchdog_tests.js b/static/tests/assets_watchdog_tests.js new file mode 100644 index 0000000..263ea5e --- /dev/null +++ b/static/tests/assets_watchdog_tests.js @@ -0,0 +1,35 @@ +/* @odoo-module */ + +import { getPyEnv, startServer } from "@bus/../tests/helpers/mock_python_environment"; +import { addBusServicesToRegistry } from "@bus/../tests/helpers/test_utils"; +import { assetsWatchdogService } from "@bus/services/assets_watchdog_service"; + +import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { click, contains } from "@web/../tests/utils"; +import { createWebClient } from "@web/../tests/webclient/helpers"; +import { browser } from "@web/core/browser/browser"; +import { registry } from "@web/core/registry"; + +QUnit.module("Bus Assets WatchDog"); + +QUnit.test("can listen on bus and displays notifications in DOM", async (assert) => { + await startServer(); + addBusServicesToRegistry(); + registry.category("services").add("assetsWatchdog", assetsWatchdogService); + patchWithCleanup(browser, { + setTimeout(fn) { + return super.setTimeout(fn, 0); + }, + location: { + reload: () => assert.step("reloadPage"), + }, + }); + await createWebClient({}); + const pyEnv = await getPyEnv(); + pyEnv["bus.bus"]._sendone("broadcast", "bundle_changed", { + server_version: "NEW_MAJOR_VERSION", + }); + await contains(".o_notification", { text: "The page appears to be out of date." }); + await click(".o_notification_buttons .btn-primary", { text: "Refresh" }); + assert.verifySteps(["reloadPage"]); +}); diff --git a/static/tests/bus_tests.js b/static/tests/bus_tests.js new file mode 100644 index 0000000..1f4b8fc --- /dev/null +++ b/static/tests/bus_tests.js @@ -0,0 +1,393 @@ +/** @odoo-module **/ + +import { addBusServicesToRegistry } from "@bus/../tests/helpers/test_utils"; +import { startServer } from "@bus/../tests/helpers/mock_python_environment"; +import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket"; +import { + waitForBusEvent, + waitForChannels, + waitNotifications, + waitUntilSubscribe, + waitForWorkerEvent, +} from "@bus/../tests/helpers/websocket_event_deferred"; +import { busParametersService } from "@bus/bus_parameters_service"; +import { WEBSOCKET_CLOSE_CODES } from "@bus/workers/websocket_worker"; + +import { makeTestEnv } from "@web/../tests/helpers/mock_env"; +import { makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils"; +import { browser } from "@web/core/browser/browser"; +import { session } from "@web/session"; + +QUnit.module("Bus"); + +QUnit.test("notifications received from the channel", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const env = await makeTestEnv({ activateMockServer: true }); + env.services["bus_service"].addChannel("lambda"); + await waitUntilSubscribe("lambda"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "beta"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "epsilon"); + await waitNotifications([env, "notifType", "beta"], [env, "notifType", "epsilon"]); +}); + +QUnit.test("notifications not received after stoping the service", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const firstTabEnv = await makeTestEnv({ activateMockServer: true }); + const secondTabEnv = await makeTestEnv({ activateMockServer: true }); + firstTabEnv.services["bus_service"].start(); + secondTabEnv.services["bus_service"].start(); + firstTabEnv.services["bus_service"].addChannel("lambda"); + await waitUntilSubscribe("lambda"); + // both tabs should receive the notification + pyEnv["bus.bus"]._sendone("lambda", "notifType", "beta"); + await waitNotifications( + [firstTabEnv, "notifType", "beta"], + [secondTabEnv, "notifType", "beta"] + ); + secondTabEnv.services["bus_service"].stop(); + await waitForWorkerEvent("leave"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "epsilon"); + await waitNotifications( + [firstTabEnv, "notifType", "epsilon"], + [secondTabEnv, "notifType", "epsilon", { received: false }] + ); +}); + +QUnit.test("notifications still received after disconnect/reconnect", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const env = await makeTestEnv({ activateMockServer: true }); + env.services["bus_service"].addChannel("lambda"); + await Promise.all([waitForBusEvent(env, "connect"), waitForChannels(["lambda"])]); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "beta"); + await waitNotifications([env, "notifType", "beta"]); + pyEnv.simulateConnectionLost(WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE); + await waitForBusEvent(env, "reconnect"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "gamma"); + await waitNotifications([env, "notifType", "gamma"]); +}); + +QUnit.test("tabs share message from a channel", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const mainEnv = await makeTestEnv({ activateMockServer: true }); + mainEnv.services["bus_service"].addChannel("lambda"); + const slaveEnv = await makeTestEnv(); + slaveEnv.services["bus_service"].addChannel("lambda"); + await waitUntilSubscribe("lambda"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "beta"); + await waitNotifications([mainEnv, "notifType", "beta"], [slaveEnv, "notifType", "beta"]); +}); + +QUnit.test("second tab still receives notifications after main pagehide", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const mainEnv = await makeTestEnv({ activateMockServer: true }); + mainEnv.services["bus_service"].addChannel("lambda"); + // prevent second tab from receiving pagehide event. + patchWithCleanup(browser, { + addEventListener(eventName, callback) { + if (eventName === "pagehide") { + return; + } + super.addEventListener(eventName, callback); + }, + }); + const secondEnv = await makeTestEnv({ activateMockServer: true }); + secondEnv.services["bus_service"].addChannel("lambda"); + await waitUntilSubscribe("lambda"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "beta"); + await waitNotifications([mainEnv, "notifType", "beta"], [secondEnv, "notifType", "beta"]); + // simulate unloading main + window.dispatchEvent(new Event("pagehide")); + await waitForWorkerEvent("leave"); + pyEnv["bus.bus"]._sendone("lambda", "notifType", "gamma"); + await waitNotifications( + [mainEnv, "notifType", "gamma", { received: false }], + [secondEnv, "notifType", "gamma"] + ); +}); + +QUnit.test("two tabs adding a different channel", async () => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const firstTabEnv = await makeTestEnv({ activateMockServer: true }); + const secondTabEnv = await makeTestEnv({ activateMockServer: true }); + firstTabEnv.services["bus_service"].addChannel("alpha"); + secondTabEnv.services["bus_service"].addChannel("beta"); + await waitUntilSubscribe("alpha", "beta"); + pyEnv["bus.bus"]._sendmany([ + ["alpha", "notifType", "alpha"], + ["beta", "notifType", "beta"], + ]); + await waitNotifications( + [firstTabEnv, "notifType", "alpha"], + [secondTabEnv, "notifType", "alpha"], + [firstTabEnv, "notifType", "beta"], + [secondTabEnv, "notifType", "beta"] + ); +}); + +QUnit.test("channel management from multiple tabs", async (assert) => { + await startServer(); + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup({ + _sendToServer({ event_name, data }) { + assert.step(`${event_name} - [${data.channels.toString()}]`); + super._sendToServer(...arguments); + }, + }); + const firstTabEnv = await makeTestEnv({ activateMockServer: true }); + const secTabEnv = await makeTestEnv({ activateMockServer: true }); + firstTabEnv.services["bus_service"].addChannel("channel1"); + await waitForChannels(["channel1"]); + // this should not trigger a subscription since the channel1 was + // aleady known. + secTabEnv.services["bus_service"].addChannel("channel1"); + await waitForChannels(["channel1"]); + // removing channel1 from first tab should not trigger + // re-subscription since the second tab still listens to this + // channel. + firstTabEnv.services["bus_service"].deleteChannel("channel1"); + await waitForChannels(["channel1"], { operation: "delete" }); + // this should trigger a subscription since the channel2 was not + // known. + secTabEnv.services["bus_service"].addChannel("channel2"); + await waitUntilSubscribe("channel2"); + assert.verifySteps(["subscribe - [channel1]", "subscribe - [channel1,channel2]"]); +}); + +QUnit.test("channels subscription after disconnection", async (assert) => { + await startServer(); + addBusServicesToRegistry(); + const worker = patchWebsocketWorkerWithCleanup(); + const env = await makeTestEnv({ activateMockServer: true }); + env.services["bus_service"].start(); + await waitUntilSubscribe(); + worker.websocket.close(WEBSOCKET_CLOSE_CODES.KEEP_ALIVE_TIMEOUT); + await Promise.all([waitForBusEvent(env, "reconnect"), waitUntilSubscribe()]); + assert.ok( + true, + "No error means waitUntilSubscribe resolves twice thus two subscriptions were triggered as expected" + ); +}); + +QUnit.test("Last notification id is passed to the worker on service start", async (assert) => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + let updateLastNotificationDeferred = makeDeferred(); + patchWebsocketWorkerWithCleanup({ + _onClientMessage(_, { action, data }) { + if (action === "initialize_connection") { + assert.step(`${action} - ${data["lastNotificationId"]}`); + updateLastNotificationDeferred.resolve(); + } + return super._onClientMessage(...arguments); + }, + }); + const env1 = await makeTestEnv(); + env1.services["bus_service"].start(); + env1.services["bus_service"].addChannel("lambda"); + await waitUntilSubscribe("lambda"); + await updateLastNotificationDeferred; + // First bus service has never received notifications thus the + // default is 0. + assert.verifySteps(["initialize_connection - 0"]); + pyEnv["bus.bus"]._sendmany([ + ["lambda", "notifType", "beta"], + ["lambda", "notifType", "beta"], + ]); + await waitForBusEvent(env1, "notification"); + updateLastNotificationDeferred = makeDeferred(); + const env2 = await makeTestEnv(); + await env2.services["bus_service"].start(); + await updateLastNotificationDeferred; + // Second bus service sends the last known notification id. + assert.verifySteps([`initialize_connection - 2`]); +}); + +QUnit.test("Websocket disconnects upon user log out", async () => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + // first tab connects to the worker with user logged. + patchWithCleanup(session, { user_id: 1 }); + const firstTabEnv = await makeTestEnv(); + firstTabEnv.services["bus_service"].start(); + await waitForBusEvent(firstTabEnv, "connect"); + // second tab connects to the worker after disconnection: user_id + // is now false. + patchWithCleanup(session, { user_id: false }); + const env2 = await makeTestEnv(); + env2.services["bus_service"].start(); + await waitForBusEvent(firstTabEnv, "disconnect"); +}); + +QUnit.test("Websocket reconnects upon user log in", async () => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + // first tab connects to the worker with no user logged. + patchWithCleanup(session, { user_id: false }); + const firstTabEnv = await makeTestEnv(); + firstTabEnv.services["bus_service"].start(); + await waitForBusEvent(firstTabEnv, "connect"); + // second tab connects to the worker after connection: user_id + // is now set. + patchWithCleanup(session, { user_id: 1 }); + const secondTabEnv = await makeTestEnv(); + secondTabEnv.services["bus_service"].start(); + await Promise.all([ + waitForBusEvent(firstTabEnv, "disconnect"), + waitForBusEvent(firstTabEnv, "connect"), + ]); +}); + +QUnit.test("WebSocket connects with URL corresponding to given serverURL", async (assert) => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + const serverURL = "http://random-website.com"; + patchWithCleanup(busParametersService, { + start() { + return { + ...super.start(...arguments), + serverURL, + }; + }, + }); + const websocketCreatedDeferred = makeDeferred(); + patchWithCleanup(window, { + WebSocket: function (url) { + assert.step(url); + websocketCreatedDeferred.resolve(); + return new EventTarget(); + }, + }); + const env = await makeTestEnv(); + env.services["bus_service"].start(); + await websocketCreatedDeferred; + assert.verifySteps([`${serverURL.replace("http", "ws")}/websocket`]); +}); + +QUnit.test("Disconnect on offline, re-connect on online", async () => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + const env = await makeTestEnv(); + await env.services["bus_service"].start(); + await waitForBusEvent(env, "connect"); + window.dispatchEvent(new Event("offline")); + await waitForBusEvent(env, "disconnect"); + window.dispatchEvent(new Event("online")); + await waitForBusEvent(env, "connect"); +}); + +QUnit.test("No disconnect on change offline/online when bus inactive", async () => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + const env = await makeTestEnv(); + window.dispatchEvent(new Event("offline")); + await waitForBusEvent(env, "disconnect", { received: false }); + window.dispatchEvent(new Event("online")); + await waitForBusEvent(env, "connect", { received: false }); +}); + +QUnit.test("Can reconnect after late close event", async (assert) => { + addBusServicesToRegistry(); + let subscribeSent = 0; + const closeDeferred = makeDeferred(); + const worker = patchWebsocketWorkerWithCleanup({ + _sendToServer({ event_name }) { + if (event_name === "subscribe") { + subscribeSent++; + } + }, + }); + const pyEnv = await startServer(); + const env = await makeTestEnv(); + env.services["bus_service"].start(); + await waitForBusEvent(env, "connect"); + patchWithCleanup(worker.websocket, { + close(code = WEBSOCKET_CLOSE_CODES.CLEAN, reason) { + this.readyState = 2; + if (code === WEBSOCKET_CLOSE_CODES.CLEAN) { + closeDeferred.then(() => { + // Simulate that the connection could not be closed cleanly. + super.close(WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE, reason); + }); + } else { + super.close(code, reason); + } + }, + }); + // Connection will be closed when passing offline. But the close event + // will be delayed to come after the next open event. The connection + // will thus be in the closing state in the meantime. + window.dispatchEvent(new Event("offline")); + // Worker reconnects upon the reception of the online event. + window.dispatchEvent(new Event("online")); + await waitForBusEvent(env, "disconnect"); + await waitForBusEvent(env, "connect"); + // Trigger the close event, it shouldn't have any effect since it is + // related to an old connection that is no longer in use. + closeDeferred.resolve(); + await waitForBusEvent(env, "disconnect", { received: false }); + // Server closes the connection, the worker should reconnect. + pyEnv.simulateConnectionLost(WEBSOCKET_CLOSE_CODES.KEEP_ALIVE_TIMEOUT); + await waitForBusEvent(env, "reconnecting"); + await waitForBusEvent(env, "reconnect"); + // 3 connections were opened, so 3 subscriptions are expected. + assert.strictEqual(subscribeSent, 3); +}); + +QUnit.test("Fallback on simple worker when shared worker failed to initialize", async (assert) => { + addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); + const originalSharedWorker = browser.SharedWorker; + const originalWorker = browser.Worker; + patchWithCleanup(browser, { + SharedWorker: function (url, options) { + assert.step("shared-worker creation"); + const sw = new originalSharedWorker(url, options); + // Simulate error during shared worker creation. + setTimeout(() => sw.dispatchEvent(new Event("error"))); + return sw; + }, + Worker: function (url, options) { + assert.step("worker creation"); + return new originalWorker(url, options); + }, + }); + patchWithCleanup(window.console, { + warn(message) { + assert.step(message); + }, + }); + const env = await makeTestEnv(); + env.services["bus_service"].start(); + await waitForBusEvent(env, "connect"); + assert.verifySteps([ + "shared-worker creation", + 'Error while loading "bus_service" SharedWorker, fallback on Worker.', + "worker creation", + ]); +}); + +QUnit.test("subscribe to single notification", async (assert) => { + addBusServicesToRegistry(); + const pyEnv = await startServer(); + const env = await makeTestEnv({ activateMockServer: true }); + env.services["bus_service"].start(); + const messageReceivedDeferred = makeDeferred(); + env.services["bus_service"].subscribe("message", (payload) => { + assert.deepEqual({ body: "hello", id: 1 }, payload); + assert.step("message"); + messageReceivedDeferred.resolve(); + }); + await waitUntilSubscribe(); + pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "message", { + body: "hello", + id: 1, + }); + await messageReceivedDeferred; + assert.verifySteps(["message"]); +}); diff --git a/static/tests/helpers/mock_python_environment.js b/static/tests/helpers/mock_python_environment.js new file mode 100644 index 0000000..88ff1a4 --- /dev/null +++ b/static/tests/helpers/mock_python_environment.js @@ -0,0 +1,342 @@ +/** @odoo-module **/ + +import { TEST_USER_IDS } from "@bus/../tests/helpers/test_constants"; + +import { registry } from "@web/core/registry"; +import { registerCleanup } from "@web/../tests/helpers/cleanup"; +import { makeMockServer } from "@web/../tests/helpers/mock_server"; +import { serializeDateTime, serializeDate } from "@web/core/l10n/dates"; +const { DateTime } = luxon; + +const modelDefinitionsPromise = new Promise((resolve) => { + QUnit.begin(() => resolve(getModelDefinitions())); +}); + +/** + * Fetch model definitions from the server then insert fields present in the + * `bus.model.definitions` registry. Use `addModelNamesToFetch`/`insertModelFields` + * helpers in order to add models to be fetched, default values to the fields, + * fields to a model definition. + * + * @return {Map} A map from model names to model fields definitions. + * @see model_definitions_setup.js + */ +async function getModelDefinitions() { + const modelDefinitionsRegistry = registry.category("bus.model.definitions"); + const modelNamesToFetch = modelDefinitionsRegistry.get("modelNamesToFetch"); + const fieldsToInsertRegistry = modelDefinitionsRegistry.category("fieldsToInsert"); + + // fetch the model definitions. + const formData = new FormData(); + formData.append("csrf_token", odoo.csrf_token); + formData.append("model_names_to_fetch", JSON.stringify(modelNamesToFetch)); + const response = await window.fetch("/bus/get_model_definitions", { + body: formData, + method: "POST", + }); + if (response.status !== 200) { + throw new Error("Error while fetching required models"); + } + const modelDefinitions = new Map(Object.entries(await response.json())); + + for (const [modelName, fields] of modelDefinitions) { + // insert fields present in the fieldsToInsert registry : if the field + // exists, update its default value according to the one in the + // registry; If it does not exist, add it to the model definition. + const fieldNamesToFieldToInsert = fieldsToInsertRegistry.category(modelName).getEntries(); + for (const [fname, fieldToInsert] of fieldNamesToFieldToInsert) { + if (fname in fields) { + fields[fname].default = fieldToInsert.default; + } else { + fields[fname] = fieldToInsert; + } + } + // apply default values for date like fields if none was passed. + for (const fname in fields) { + const field = fields[fname]; + if (["date", "datetime"].includes(field.type) && !field.default) { + const defaultFieldValue = + field.type === "date" + ? () => serializeDate(DateTime.utc()) + : () => serializeDateTime(DateTime.utc()); + field.default = defaultFieldValue; + } else if (fname === "active" && !("default" in field)) { + // records are active by default. + field.default = true; + } + } + } + // add models present in the fake models registry to the model definitions. + const fakeModels = modelDefinitionsRegistry.category("fakeModels").getEntries(); + for (const [modelName, fields] of fakeModels) { + modelDefinitions.set(modelName, fields); + } + return modelDefinitions; +} + +let _cookie = {}; +export const pyEnvTarget = { + cookie: { + get(key) { + return _cookie[key]; + }, + set(key, value) { + _cookie[key] = value; + }, + delete(key) { + delete _cookie[key]; + }, + }, + _authenticate(user) { + if (!user) { + throw new Error("Unauthorized"); + } + this.cookie.set("sid", user.id); + }, + /** + * Authenticate a user on the mock server given its login + * and password. + * + * @param {string} login + * @param {string|} password + */ + authenticate(login, password) { + const user = this.mockServer.getRecords( + "res.users", + [ + ["login", "=", login], + ["password", "=", password], + ], + { active_test: false } + )[0]; + this._authenticate(user); + this.cookie.set("authenticated_user_sid", this.cookie.get("sid")); + }, + /** + * Logout the current user. + */ + logout() { + if (this.cookie.get("authenticated_user_sid") === this.cookie.get("sid")) { + this.cookie.delete("authenticated_user_sid"); + } + this.cookie.delete("sid"); + const [publicUser] = this.mockServer.getRecords( + "res.users", + [["id", "=", this.publicUserId]], + { active_test: false } + ); + this.authenticate(publicUser.login, publicUser.password); + }, + /** + * Execute the provided function with the given user + * authenticated then restore the original user. + * + * @param {number} userId + * @param {Function} fn + */ + async withUser(userId, fn) { + const user = this.currentUser; + const targetUser = this.mockServer.getRecords("res.users", [["id", "=", userId]], { + active_test: false, + })[0]; + this._authenticate(targetUser); + let result; + try { + result = await fn(); + } finally { + if (user) { + this._authenticate(user); + } else { + this.logout(); + } + } + return result; + }, + /** + * The current user, either the one authenticated or the one + * impersonated by `withUser`. + */ + get currentUser() { + let user; + const currentUserId = this.cookie.get("sid"); + if ("res.users" in this.mockServer.models && currentUserId) { + user = this.mockServer.getRecords("res.users", [["id", "=", currentUserId]], { + active_test: false, + })[0]; + user = user ? { ...user, _is_public: () => user.id === this.publicUserId } : undefined; + } + return user; + }, + /** + * The current partner, either the one of the current user or + * the one of the user impersonated by `withUser`. + */ + get currentPartner() { + if ("res.partner" in this.mockServer.models && this.currentUser?.partner_id) { + return this.mockServer.getRecords( + "res.partner", + [["id", "=", this.currentUser?.partner_id]], + { active_test: false } + )[0]; + } + return undefined; + }, + get currentUserId() { + return this.currentUser?.id; + }, + get currentPartnerId() { + return this.currentPartner?.id; + }, + getData() { + return this.mockServer.models; + }, + getViews() { + return this.mockServer.archs; + }, + simulateConnectionLost(closeCode) { + this.mockServer._simulateConnectionLost(closeCode); + }, + ...TEST_USER_IDS, +}; + +let pyEnv; +/** + * Creates an environment that can be used to setup test data as well as + * creating data after test start. + * + * @param {Object} serverData serverData to pass to the mockServer. + * @param {Object} [serverData.action] actions to be passed to the mock + * server. + * @param {Object} [serverData.views] views to be passed to the mock + * server. + * @returns {Object} An environment that can be used to interact with + * the mock server (creation, deletion, update of records...) + */ +export async function startServer({ actions, views = {} } = {}) { + const models = {}; + const modelDefinitions = await modelDefinitionsPromise; + const recordsToInsertRegistry = registry + .category("bus.model.definitions") + .category("recordsToInsert"); + for (const [modelName, fields] of modelDefinitions) { + const records = []; + if (recordsToInsertRegistry.contains(modelName)) { + // prevent tests from mutating the records. + records.push(...JSON.parse(JSON.stringify(recordsToInsertRegistry.get(modelName)))); + } + models[modelName] = { fields: { ...fields }, records }; + + // generate default views for this model if none were passed. + const viewArchsSubRegistries = registry.category("bus.view.archs").subRegistries; + for (const [viewType, archsRegistry] of Object.entries(viewArchsSubRegistries)) { + views[`${modelName},false,${viewType}`] = + views[`${modelName},false,${viewType}`] || + archsRegistry.get(modelName, archsRegistry.get("default")); + } + } + pyEnv = new Proxy(pyEnvTarget, { + get(target, name) { + if (name in target) { + return target[name]; + } + const modelAPI = { + /** + * Simulate a 'create' operation on a model. + * + * @param {Object[]|Object} values records to be created. + * @returns {integer[]|integer} array of ids if more than one value was passed, + * id of created record otherwise. + */ + create(values) { + if (!values) { + return; + } + if (!Array.isArray(values)) { + values = [values]; + } + const recordIds = values.map((value) => + target.mockServer.mockCreate(name, value) + ); + return recordIds.length === 1 ? recordIds[0] : recordIds; + }, + /** + * Simulate a 'search' operation on a model. + * + * @param {Array} domain + * @param {Object} context + * @returns {integer[]} array of ids corresponding to the given domain. + */ + search(domain, context = {}) { + return target.mockServer.mockSearch(name, [domain], context); + }, + /** + * Simulate a `search_count` operation on a model. + * + * @param {Array} domain + * @return {number} count of records matching the given domain. + */ + searchCount(domain) { + return this.search(domain).length; + }, + /** + * Simulate a 'search_read' operation on a model. + * + * @param {Array} domain + * @param {Object} kwargs + * @returns {Object[]} array of records corresponding to the given domain. + */ + searchRead(domain, kwargs = {}) { + return target.mockServer.mockSearchRead(name, [domain], kwargs); + }, + /** + * Simulate an 'unlink' operation on a model. + * + * @param {integer[]} ids + * @returns {boolean} mockServer 'unlink' method always returns true. + */ + unlink(ids) { + return target.mockServer.mockUnlink(name, [ids]); + }, + /** + * Simulate a 'write' operation on a model. + * + * @param {integer[]} ids ids of records to write on. + * @param {Object} values values to write on the records matching given ids. + * @returns {boolean} mockServer 'write' method always returns true. + */ + write(ids, values) { + return target.mockServer.mockWrite(name, [ids, values]); + }, + }; + if (name === "bus.bus") { + modelAPI["_sendone"] = target.mockServer._mockBusBus__sendone.bind( + target.mockServer + ); + modelAPI["_sendmany"] = target.mockServer._mockBusBus__sendmany.bind( + target.mockServer + ); + } + return modelAPI; + }, + }); + pyEnv["mockServer"] = await makeMockServer({ actions, models, views }); + pyEnv["mockServer"].pyEnv = pyEnv; + registerCleanup(() => { + pyEnv = undefined; + _cookie = {}; + }); + if ("res.users" in pyEnv.mockServer.models) { + const adminUser = pyEnv["res.users"].searchRead([["id", "=", pyEnv.adminUserId]])[0]; + pyEnv.authenticate(adminUser.login, adminUser.password); + } + return pyEnv; +} + +/** + * + * @returns {Object} An environment that can be used to interact with the mock + * server (creation, deletion, update of records...) + */ +export function getPyEnv() { + return pyEnv || startServer(); +} diff --git a/static/tests/helpers/mock_server.js b/static/tests/helpers/mock_server.js new file mode 100644 index 0000000..7667fe4 --- /dev/null +++ b/static/tests/helpers/mock_server.js @@ -0,0 +1,172 @@ +/** @odoo-module **/ + +import { TEST_USER_IDS } from "@bus/../tests/helpers/test_constants"; +import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket"; + +import { patch } from "@web/core/utils/patch"; +import { MockServer } from "@web/../tests/helpers/mock_server"; +import { registry } from "@web/core/registry"; + +QUnit.testDone(() => { + const callbackRegistry = registry.category("mock_server_websocket_callbacks"); + callbackRegistry.getEntries().map(([key]) => callbackRegistry.remove(key)); +}); + +patch(MockServer.prototype, { + init() { + super.init(...arguments); + Object.assign(this, TEST_USER_IDS); + const self = this; + this.notificationQueue = []; + this.websocketWorker = patchWebsocketWorkerWithCleanup({ + _sendToServer(message) { + self._performWebsocketRequest(message); + super._sendToServer(message); + }, + }); + this.lastBusNotificationId = 0; + this.channelsByUser = {}; + for (const modelName in this.models) { + const records = Array.isArray(this.models[modelName].records) + ? this.models[modelName].records + : []; + for (const record of records) { + Object.defineProperty(record, "__model", { value: modelName }); + } + } + }, + + mockCreate(modelName, valsList, kwargs = {}) { + const result = super.mockCreate(modelName, valsList, kwargs); + const returnArrayOfIds = Array.isArray(result); + const recordIds = Array.isArray(result) ? result : [result]; + for (const recordId of recordIds) { + const record = this.models[modelName].records.find((r) => r.id === recordId); + Object.defineProperty(record, "__model", { value: modelName }); + } + return returnArrayOfIds ? recordIds : recordIds[0]; + }, + + mockSearchRead(modelName, domain, kwargs = {}) { + const records = super.mockSearchRead(modelName, domain, kwargs); + for (const record of records) { + Object.defineProperty(record, "__model", { value: modelName }); + } + return records; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @param {Object} message Message sent through the websocket to the + * server. + * @param {string} [message.event_name] + * @param {any} [message.data] + */ + _performWebsocketRequest({ event_name, data }) { + if (event_name === "update_presence") { + const { inactivity_period, im_status_ids_by_model } = data; + this._mockIrWebsocket__updatePresence(inactivity_period, im_status_ids_by_model); + } else if (event_name === "subscribe") { + const { channels } = data; + this.channelsByUser[this.pyEnv?.currentUser] = this.pyEnv + ? this._mockIrWebsocket__buildBusChannelList(channels) + : channels; + } + const callbackFn = registry + .category("mock_server_websocket_callbacks") + .get(event_name, null); + if (callbackFn) { + callbackFn(data); + } + }, + /** + * Simulates `_sendone` on `bus.bus`. + * + * @param {string} channel + * @param {string} notificationType + * @param {any} message + */ + _mockBusBus__sendone(channel, notificationType, message) { + this._mockBusBus__sendmany([[channel, notificationType, message]]); + }, + /** + * Simulates `_sendmany` on `bus.bus`. + * + * @param {Array} notifications + */ + _mockBusBus__sendmany(notifications) { + if (!notifications.length) { + return; + } + const values = []; + const authenticatedUserId = + "res.users" in this.models + ? this.pyEnv.cookie.get("authenticated_user_sid") + : undefined; + const authenticatedUser = authenticatedUserId + ? this.pyEnv["res.users"].searchRead([["id", "=", authenticatedUserId]], { + context: { active_test: false }, + })[0] + : null; + const channels = + this.channelsByUser[authenticatedUser] ?? this._mockIrWebsocket__buildBusChannelList(); + notifications = notifications.filter(([target]) => + channels.some((channel) => { + if (typeof target === "string") { + return channel === target; + } + if (Array.isArray(channel) !== Array.isArray(target)) { + return false; + } + if (Array.isArray(channel)) { + const { __model: cModel, id: cId } = channel[0]; + const { __model: tModel, id: tId } = target[0]; + return cModel === tModel && cId === tId && channel[1] === target[1]; + } + return channel?.__model === target.__model && channel?.id === target?.id; + }) + ); + if (notifications.length === 0) { + return; + } + for (const notification of notifications) { + const [type, payload] = notification.slice(1, notification.length); + values.push({ id: ++this.lastBusNotificationId, message: { payload, type } }); + } + this.notificationQueue.push(...values); + if (this.notificationQueue.length === values.length) { + this._planNotificationSending(); + } + }, + + /** + * Helper to send the pending notifications to the client. This method is + * push to the micro task queue to simulate server-side batching of + * notifications. + */ + _planNotificationSending() { + queueMicrotask(() => { + if (this.debug) { + console.group("%c[BUS]", "color: #c6e; font-weight: bold;"); + for (const { message } of this.notificationQueue) { + console.log(message.type, message.payload); + } + console.groupEnd(); + } + this.websocketWorker.broadcast("notification", this.notificationQueue); + this.notificationQueue = []; + }); + }, + /** + * Simulate the lost of the connection by simulating a closeEvent on + * the worker websocket. + * + * @param {number} closeCode the code to close the connection with. + */ + _simulateConnectionLost(closeCode) { + this.websocketWorker.websocket.close(closeCode); + }, +}); diff --git a/static/tests/helpers/mock_server/models/ir_websocket.js b/static/tests/helpers/mock_server/models/ir_websocket.js new file mode 100644 index 0000000..0c76a52 --- /dev/null +++ b/static/tests/helpers/mock_server/models/ir_websocket.js @@ -0,0 +1,58 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { MockServer } from "@web/../tests/helpers/mock_server"; + +patch(MockServer.prototype, { + /** + * Simulates `_update_presence` on `ir.websocket`. + * + * @param inactivityPeriod + * @param imStatusIdsByModel + */ + _mockIrWebsocket__updatePresence(inactivityPeriod, imStatusIdsByModel) { + const imStatusNotifications = this._mockIrWebsocket__getImStatus(imStatusIdsByModel); + if (Object.keys(imStatusNotifications).length > 0) { + this._mockBusBus__sendone( + this.pyEnv.currentPartner, + "mail.record/insert", + imStatusNotifications + ); + } + }, + /** + * Simulates `_get_im_status` on `ir.websocket`. + * + * @param {Object} imStatusIdsByModel + * @param {Number[]|undefined} res.partner ids of res.partners whose im_status + * should be monitored. + */ + _mockIrWebsocket__getImStatus(imStatusIdsByModel) { + const imStatus = {}; + const { "res.partner": partnerIds } = imStatusIdsByModel; + if (partnerIds) { + imStatus["Persona"] = this.mockSearchRead("res.partner", [[["id", "in", partnerIds]]], { + context: { active_test: false }, + fields: ["im_status"], + }).map((p) => ({ ...p, type: "partner" })); + } + return imStatus; + }, + /** + * Simulates `_build_bus_channel_list` on `ir.websocket`. + */ + _mockIrWebsocket__buildBusChannelList(channels = []) { + channels = [...channels]; + channels.push("broadcast"); + const authenticatedUserId = this.pyEnv.cookie.get("authenticated_user_sid"); + const authenticatedPartner = authenticatedUserId + ? this.pyEnv["res.partner"].searchRead([["user_ids", "in", [authenticatedUserId]]], { + context: { active_test: false }, + })[0] + : null; + if (authenticatedPartner) { + channels.push(authenticatedPartner); + } + return channels; + }, +}); diff --git a/static/tests/helpers/mock_services.js b/static/tests/helpers/mock_services.js new file mode 100644 index 0000000..5452c70 --- /dev/null +++ b/static/tests/helpers/mock_services.js @@ -0,0 +1,15 @@ +/** @odoo-module **/ + +import { presenceService } from "@bus/services/presence_service"; + +export function makeFakePresenceService(params = {}) { + return { + ...presenceService, + start(env) { + return { + ...presenceService.start(env), + ...params, + }; + }, + }; +} diff --git a/static/tests/helpers/mock_websocket.js b/static/tests/helpers/mock_websocket.js new file mode 100644 index 0000000..32b5fa5 --- /dev/null +++ b/static/tests/helpers/mock_websocket.js @@ -0,0 +1,108 @@ +/** @odoo-module **/ + +import { WebsocketWorker } from "@bus/workers/websocket_worker"; +import { browser } from "@web/core/browser/browser"; +import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { registerCleanup } from "@web/../tests/helpers/cleanup"; + +class WebSocketMock extends EventTarget { + constructor() { + super(); + this.readyState = 0; + + queueMicrotask(() => { + this.readyState = 1; + const openEv = new Event("open"); + this.onopen(openEv); + this.dispatchEvent(openEv); + }); + } + + close(code = 1000, reason) { + this.readyState = 3; + const closeEv = new CloseEvent("close", { + code, + reason, + wasClean: code === 1000, + }); + this.onclose(closeEv); + this.dispatchEvent(closeEv); + } + + onclose(closeEv) {} + onerror(errorEv) {} + onopen(openEv) {} + + send(data) { + if (this.readyState !== 1) { + const errorEv = new Event("error"); + this.onerror(errorEv); + this.dispatchEvent(errorEv); + throw new DOMException("Failed to execute 'send' on 'WebSocket': State is not OPEN"); + } + } +} + +class SharedWorkerMock extends EventTarget { + constructor(websocketWorker) { + super(); + this._websocketWorker = websocketWorker; + this._messageChannel = new MessageChannel(); + this.port = this._messageChannel.port1; + // port 1 should be started by the service itself. + this._messageChannel.port2.start(); + this._websocketWorker.registerClient(this._messageChannel.port2); + } +} + +class WorkerMock extends SharedWorkerMock { + constructor(websocketWorker) { + super(websocketWorker); + this.port.start(); + this.postMessage = this.port.postMessage.bind(this.port); + } +} + +let websocketWorker; +/** + * @param {*} params Parameters used to patch the websocket worker. + * @returns {WebsocketWorker} Instance of the worker which will run during the + * test. Usefull to interact with the worker in order to test the + * websocket behavior. + */ +export function patchWebsocketWorkerWithCleanup(params = {}) { + patchWithCleanup(window, { + WebSocket: function () { + return new WebSocketMock(); + }, + }); + patchWithCleanup(websocketWorker || WebsocketWorker.prototype, params); + websocketWorker = websocketWorker || new WebsocketWorker(); + websocketWorker.INITIAL_RECONNECT_DELAY = 0; + websocketWorker.RECONNECT_JITTER = 0; + patchWithCleanup(browser, { + SharedWorker: function () { + const sharedWorker = new SharedWorkerMock(websocketWorker); + registerCleanup(() => { + sharedWorker._messageChannel.port1.close(); + sharedWorker._messageChannel.port2.close(); + }); + return sharedWorker; + }, + Worker: function () { + const worker = new WorkerMock(websocketWorker); + registerCleanup(() => { + worker._messageChannel.port1.close(); + worker._messageChannel.port2.close(); + }); + return worker; + }, + }); + registerCleanup(() => { + if (websocketWorker) { + clearTimeout(websocketWorker.connectTimeout); + websocketWorker = null; + } + }); + return websocketWorker; +} diff --git a/static/tests/helpers/model_definitions_helpers.js b/static/tests/helpers/model_definitions_helpers.js new file mode 100644 index 0000000..74ea54c --- /dev/null +++ b/static/tests/helpers/model_definitions_helpers.js @@ -0,0 +1,57 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; + +const modelDefinitionsRegistry = registry.category("bus.model.definitions"); +const customModelFieldsRegistry = modelDefinitionsRegistry.category("fieldsToInsert"); +const recordsToInsertRegistry = modelDefinitionsRegistry.category("recordsToInsert"); +const fakeModelsRegistry = modelDefinitionsRegistry.category("fakeModels"); +/** + * Add models whose definitions need to be fetched on the server. + * + * @param {string[]} modelName + */ +export function addModelNamesToFetch(modelNames) { + if (!modelDefinitionsRegistry.contains("modelNamesToFetch")) { + modelDefinitionsRegistry.add("modelNamesToFetch", []); + } + modelDefinitionsRegistry.get("modelNamesToFetch").push(...modelNames); +} + +/** + * Add models that will be added to the model definitions. We should + * avoid to rely on fake models and use real models instead. + * + * @param {string} modelName + * @param {Object} fields + */ +export function addFakeModel(modelName, fields) { + fakeModelsRegistry.add(modelName, fields); +} + +/** + * Add model fields that are not present on the server side model's definitions + * but are required to ease testing or add default values for existing fields. + * + * @param {string} modelName + * @param {Object} fieldNamesToFields + */ +export function insertModelFields(modelName, fieldNamesToFields) { + const modelCustomFieldsRegistry = customModelFieldsRegistry.category(modelName); + for (const fname in fieldNamesToFields) { + modelCustomFieldsRegistry.add(fname, fieldNamesToFields[fname]); + } +} + +/** + * Add records to the initial server data. + * + * @param {string} modelName + * @param {Object[]} records + */ +export function insertRecords(modelName, records) { + if (!recordsToInsertRegistry.contains(modelName)) { + recordsToInsertRegistry.add(modelName, []); + } + recordsToInsertRegistry.get(modelName).push(...records); +} diff --git a/static/tests/helpers/model_definitions_setup.js b/static/tests/helpers/model_definitions_setup.js new file mode 100644 index 0000000..3c660d3 --- /dev/null +++ b/static/tests/helpers/model_definitions_setup.js @@ -0,0 +1,79 @@ +/** @odoo-module **/ + +import { TEST_GROUP_IDS, TEST_USER_IDS } from "@bus/../tests/helpers/test_constants"; +import { + addModelNamesToFetch, + insertModelFields, + insertRecords, +} from "@bus/../tests/helpers/model_definitions_helpers"; + +//-------------------------------------------------------------------------- +// Models +//-------------------------------------------------------------------------- + +addModelNamesToFetch([ + "ir.attachment", + "ir.model", + "ir.model.fields", + "res.company", + "res.country", + "res.groups", + "res.partner", + "res.users", +]); + +//-------------------------------------------------------------------------- +// Insertion of fields +//-------------------------------------------------------------------------- + +insertModelFields("res.partner", { + description: { string: "description", type: "text" }, + is_company: { default: () => false }, +}); + +//-------------------------------------------------------------------------- +// Insertion of records +//-------------------------------------------------------------------------- + +insertRecords("res.company", [{ id: 1 }]); +insertRecords("res.groups", [{ id: TEST_GROUP_IDS.groupUserId, name: "Internal User" }]); +insertRecords("res.users", [ + { + display_name: "Your Company, Mitchell Admin", + id: TEST_USER_IDS.adminUserId, + name: "Mitchell Admin", + login: "admin", + password: "admin", + partner_id: TEST_USER_IDS.adminPartnerId, + }, + { + active: false, + display_name: "Public user", + login: "public", + password: "public", + id: TEST_USER_IDS.publicUserId, + name: "Public user", + partner_id: TEST_USER_IDS.publicPartnerId, + }, +]); +insertRecords("res.partner", [ + { + active: false, + display_name: "Public user", + name: "Public user", + id: TEST_USER_IDS.publicPartnerId, + is_public: true, + }, + { + display_name: "Your Company, Mitchell Admin", + id: TEST_USER_IDS.adminPartnerId, + name: "Mitchell Admin", + }, + { + active: false, + display_name: "OdooBot", + id: TEST_USER_IDS.odoobotId, + im_status: "bot", + name: "OdooBot", + }, +]); diff --git a/static/tests/helpers/test_constants.js b/static/tests/helpers/test_constants.js new file mode 100644 index 0000000..efbbd29 --- /dev/null +++ b/static/tests/helpers/test_constants.js @@ -0,0 +1,13 @@ +/** @odoo-module **/ + +export const TEST_GROUP_IDS = { + groupUserId: 11, +}; + +export const TEST_USER_IDS = { + odoobotId: 2, + adminPartnerId: 3, + adminUserId: 2, + publicPartnerId: 4, + publicUserId: 3, +}; diff --git a/static/tests/helpers/test_utils.js b/static/tests/helpers/test_utils.js new file mode 100644 index 0000000..110a7a7 --- /dev/null +++ b/static/tests/helpers/test_utils.js @@ -0,0 +1,17 @@ +/* @odoo-module */ + +import { busParametersService } from "@bus/bus_parameters_service"; +import { busService } from "@bus/services/bus_service"; +import { multiTabService } from "@bus/multi_tab_service"; +import { presenceService } from "@bus/services/presence_service"; + +import { registry } from "@web/core/registry"; + +export function addBusServicesToRegistry() { + registry + .category("services") + .add("bus.parameters", busParametersService) + .add("bus_service", busService) + .add("presence", presenceService) + .add("multi_tab", multiTabService); +} diff --git a/static/tests/helpers/view_definitions_setup.js b/static/tests/helpers/view_definitions_setup.js new file mode 100644 index 0000000..07a6fe8 --- /dev/null +++ b/static/tests/helpers/view_definitions_setup.js @@ -0,0 +1,30 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; + +const viewArchsRegistry = registry.category("bus.view.archs"); +const activityArchsRegistry = viewArchsRegistry.category("activity"); +const formArchsRegistry = viewArchsRegistry.category("form"); +const kanbanArchsRegistry = viewArchsRegistry.category("kanban"); +const listArchsRegistry = viewArchsRegistry.category("list"); +const searchArchsRegistry = viewArchsRegistry.category("search"); + +activityArchsRegistry.add("default", ""); +formArchsRegistry.add("default", "
"); +kanbanArchsRegistry.add("default", ""); +listArchsRegistry.add("default", ""); +searchArchsRegistry.add("default", ""); + +formArchsRegistry.add( + "res.partner", + ` + + + +
+ + + +
+ ` +); diff --git a/static/tests/helpers/websocket_event_deferred.js b/static/tests/helpers/websocket_event_deferred.js new file mode 100644 index 0000000..0ab3b94 --- /dev/null +++ b/static/tests/helpers/websocket_event_deferred.js @@ -0,0 +1,226 @@ +/* @odoo-module */ + +import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket"; + +import { registry } from "@web/core/registry"; +import { patch } from "@web/core/utils/patch"; +import { registerCleanup } from "@web/../tests/helpers/cleanup"; +import { makeDeferred } from "@web/../tests/helpers/utils"; + +// should be enough to decide whether or not notifications/channel +// subscriptions... are received. +const TIMEOUT = 500; +const callbackRegistry = registry.category("mock_server_websocket_callbacks"); + +/** + * Returns a deferred that resolves when a websocket subscription is + * done. If channels are provided, the deferred will only resolve when + * we subscribe to all of them. + * + * @param {...string} [requiredChannels] + * @returns {import("@web/core/utils/concurrency").Deferred} + */ +export function waitUntilSubscribe(...requiredChannels) { + const subscribeDeferred = makeDeferred(); + const failTimeout = setTimeout(() => { + const errMsg = `Subscription to ${JSON.stringify(requiredChannels)} not received.`; + subscribeDeferred.reject(new Error(errMsg)); + QUnit.assert.ok(false, errMsg); + }, TIMEOUT); + const lastCallback = callbackRegistry.get("subscribe", () => {}); + callbackRegistry.add( + "subscribe", + (data) => { + const { channels } = data; + lastCallback(data); + const allChannelsSubscribed = requiredChannels.every((channel) => + channels.includes(channel) + ); + if (allChannelsSubscribed) { + subscribeDeferred.resolve(); + QUnit.assert.ok( + true, + `Subscription to ${JSON.stringify(requiredChannels)} received.` + ); + clearTimeout(failTimeout); + } + }, + { force: true } + ); + return subscribeDeferred; +} + +/** + * Returns a deferred that resolves when the given channel(s) addition/deletion + * is notified to the websocket worker. + * + * @param {string[]} channels + * @param {object} [options={}] + * @param {"add"|"delete"} [options.operation="add"] + * + * @returns {import("@web/core/utils/concurrency").Deferred} */ +export function waitForChannels(channels, { operation = "add" } = {}) { + const missingChannels = new Set(channels); + const deferred = makeDeferred(); + function check({ crashOnFail = false } = {}) { + const success = missingChannels.size === 0; + if (!success && !crashOnFail) { + return; + } + unpatch(); + clearTimeout(failTimeout); + const msg = success + ? `Channel(s) [${channels.join(", ")}] ${operation === "add" ? "added" : "deleted"}.` + : `Waited ${TIMEOUT}ms for [${channels.join(", ")}] to be ${ + operation === "add" ? "added" : "deleted" + }`; + QUnit.assert.ok(success, msg); + if (success) { + deferred.resolve(); + } else { + deferred.reject(new Error(msg)); + } + } + const failTimeout = setTimeout(() => check({ crashOnFail: true }), TIMEOUT); + registerCleanup(() => { + if (missingChannels.length > 0) { + check({ crashOnFail: true }); + } + }); + const worker = patchWebsocketWorkerWithCleanup(); + const workerMethod = operation === "add" ? "_addChannel" : "_deleteChannel"; + const unpatch = patch(worker, { + async [workerMethod](client, channel) { + await super[workerMethod](client, channel); + missingChannels.delete(channel); + check(); + }, + }); + return deferred; +} + +/** + * @typedef {Object} ExpectedNotificationOptions + * @property {boolean} [received=true] + * @typedef {[env: import("@web/env").OdooEnv, notificationType: string, notificationPayload: any, options: ExpectedNotificationOptions]} ExpectedNotification + */ + +/** + * Wait for a notification to be received/not received. Returns + * a deferred that resolves when the assertion is done. + * + * @param {ExpectedNotification} notification + * @returns {import("@web/core/utils/concurrency").Deferred} + */ +function _waitNotification(notification) { + const [env, type, payload, { received = true } = {}] = notification; + const notificationDeferred = makeDeferred(); + const failTimeout = setTimeout(() => { + QUnit.assert.ok( + !received, + `Notification of type "${type}" with payload ${payload} not received.` + ); + env.services["bus_service"].removeEventListener("notification", callback); + notificationDeferred.resolve(); + }, TIMEOUT); + const callback = ({ detail: notifications }) => { + for (const notification of notifications) { + if (notification.type !== type) { + continue; + } + if ( + payload === undefined || + JSON.stringify(notification.payload) === JSON.stringify(payload) + ) { + QUnit.assert.ok( + received, + `Notification of type "${type}" with payload ${JSON.stringify( + notification.payload + )} receveived.` + ); + notificationDeferred.resolve(); + clearTimeout(failTimeout); + env.services["bus_service"].removeEventListener("notification", callback); + } + } + }; + env.services["bus_service"].addEventListener("notification", callback); + return notificationDeferred; +} + +/** + * Wait for the expected notifications to be received/not received. Returns + * a deferred that resolves when the assertion is done. + * + * @param {ExpectedNotification[]} expectedNotifications + * @returns {import("@web/core/utils/concurrency").Deferred} + */ +export function waitNotifications(...expectedNotifications) { + return Promise.all( + expectedNotifications.map((expectedNotification) => _waitNotification(expectedNotification)) + ); +} + +/** + * Returns a deferred that resolves when an event matching the given type is + * received from the bus service. + * + * @typedef {"connect"|"disconnect"|"reconnect"|"reconnecting"|"notification"} EventType + * @param {import("@web/env").OdooEnv} env + * @param {EventType} eventType + * @param {object} [options={}] + * @param {boolean} [options.received=true] + */ +export function waitForBusEvent(env, eventType, { received = true } = {}) { + const eventReceivedDeferred = makeDeferred(); + const failTimeout = setTimeout(() => { + env.services["bus_service"].removeEventListener(eventType, callback); + QUnit.assert.ok( + !received, + received + ? `Waited ${TIMEOUT}ms for ${eventType} event.` + : `Event of type "${eventType}" not received.` + ); + eventReceivedDeferred.resolve(); + }, TIMEOUT); + const callback = () => { + env.services["bus_service"].removeEventListener(eventType, callback); + QUnit.assert.ok(received, `Event of type "${eventType}" received.`); + eventReceivedDeferred.resolve(); + clearTimeout(failTimeout); + }; + env.services["bus_service"].addEventListener(eventType, callback); + return eventReceivedDeferred; +} + +/** + * Returns a deferred that resolves when an event matching the given type is + * received by the websocket worker. + * + * @param {import("@web/env").OdooEnv} env + * @param {import("@bus/workers/websocket_worker").WorkerAction} targetAction + * @param {object} [options={}] + * @param {boolean} [options.received=true] + */ +export function waitForWorkerEvent(targetAction) { + const eventReiceivedDeferred = makeDeferred(); + const failTimeout = setTimeout(() => { + unpatch(); + QUnit.assert.ok(false, `Waited ${TIMEOUT}ms for ${targetAction} to be received.`); + eventReiceivedDeferred.resolve(); + }, TIMEOUT); + const worker = patchWebsocketWorkerWithCleanup(); + const unpatch = patch(worker, { + _onClientMessage(_, { action }) { + super._onClientMessage(...arguments); + if (targetAction === action) { + unpatch(); + QUnit.assert.ok(true, `Action "${action}" received.`); + eventReiceivedDeferred.resolve(); + clearTimeout(failTimeout); + } + }, + }); + registerCleanup(unpatch); + return eventReiceivedDeferred; +} diff --git a/static/tests/multi_tab_service_tests.js b/static/tests/multi_tab_service_tests.js new file mode 100644 index 0000000..3ba887b --- /dev/null +++ b/static/tests/multi_tab_service_tests.js @@ -0,0 +1,95 @@ +/** @odoo-module **/ + +import { addBusServicesToRegistry } from "@bus/../tests/helpers/test_utils"; + +import { browser } from "@web/core/browser/browser"; +import { makeTestEnv } from "@web/../tests/helpers/mock_env"; +import { patchWithCleanup, nextTick } from "@web/../tests/helpers/utils"; + +QUnit.module("multi_tab_service_tests.js"); + +QUnit.test("multi tab service elects new master on pagehide", async function (assert) { + addBusServicesToRegistry(); + const firstTabEnv = await makeTestEnv(); + assert.ok(firstTabEnv.services["multi_tab"].isOnMainTab(), "only tab should be the main one"); + + // prevent second tab from receiving pagehide event. + patchWithCleanup(browser, { + addEventListener(eventName, callback) { + if (eventName === "pagehide") { + return; + } + super.addEventListener(eventName, callback); + }, + }); + const secondTabEnv = await makeTestEnv(); + firstTabEnv.services["multi_tab"].bus.addEventListener("no_longer_main_tab", () => + assert.step("tab1 no_longer_main_tab") + ); + secondTabEnv.services["multi_tab"].bus.addEventListener("no_longer_main_tab", () => + assert.step("tab2 no_longer_main_tab") + ); + window.dispatchEvent(new Event("pagehide")); + + // Let the multi tab elect a new main. + await nextTick(); + assert.notOk(firstTabEnv.services["multi_tab"].isOnMainTab()); + assert.ok(secondTabEnv.services["multi_tab"].isOnMainTab()); + assert.verifySteps(["tab1 no_longer_main_tab"]); +}); + +QUnit.test("multi tab allow to share values between tabs", async function (assert) { + addBusServicesToRegistry(); + const firstTabEnv = await makeTestEnv(); + const secondTabEnv = await makeTestEnv(); + + firstTabEnv.services["multi_tab"].setSharedValue("foo", 1); + assert.deepEqual(secondTabEnv.services["multi_tab"].getSharedValue("foo"), 1); + firstTabEnv.services["multi_tab"].setSharedValue("foo", 2); + assert.deepEqual(secondTabEnv.services["multi_tab"].getSharedValue("foo"), 2); + + firstTabEnv.services["multi_tab"].removeSharedValue("foo"); + assert.notOk(secondTabEnv.services["multi_tab"].getSharedValue("foo")); +}); + +QUnit.test("multi tab triggers shared_value_updated", async function (assert) { + addBusServicesToRegistry(); + const firstTabEnv = await makeTestEnv(); + const secondTabEnv = await makeTestEnv(); + + secondTabEnv.services["multi_tab"].bus.addEventListener( + "shared_value_updated", + ({ detail }) => { + assert.step(`${detail.key} - ${JSON.parse(detail.newValue)}`); + } + ); + firstTabEnv.services["multi_tab"].setSharedValue("foo", "bar"); + firstTabEnv.services["multi_tab"].setSharedValue("foo", "foo"); + firstTabEnv.services["multi_tab"].removeSharedValue("foo"); + + await nextTick(); + assert.verifySteps(["foo - bar", "foo - foo", "foo - null"]); +}); + +QUnit.test("multi tab triggers become_master", async function (assert) { + addBusServicesToRegistry(); + await makeTestEnv(); + // prevent second tab from receiving pagehide event. + patchWithCleanup(browser, { + addEventListener(eventName, callback) { + if (eventName === "pagehide") { + return; + } + super.addEventListener(eventName, callback); + }, + }); + const secondTabEnv = await makeTestEnv(); + secondTabEnv.services["multi_tab"].bus.addEventListener("become_main_tab", () => + assert.step("become_main_tab") + ); + window.dispatchEvent(new Event("pagehide")); + + // Let the multi tab elect a new main. + await nextTick(); + assert.verifySteps(["become_main_tab"]); +}); diff --git a/static/tests/simple_notification_tests.js b/static/tests/simple_notification_tests.js new file mode 100644 index 0000000..e11e24f --- /dev/null +++ b/static/tests/simple_notification_tests.js @@ -0,0 +1,69 @@ +/* @odoo-module */ + +import { simpleNotificationService } from "@bus/simple_notification_service"; +import { addBusServicesToRegistry } from "@bus/../tests/helpers/test_utils"; +import { startServer } from "@bus/../tests/helpers/mock_python_environment"; + +import { registry } from "@web/core/registry"; +import { browser } from "@web/core/browser/browser"; +import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { contains } from "@web/../tests/utils"; +import { createWebClient } from "@web/../tests/webclient/helpers"; + +QUnit.module("simple_notification"); + +QUnit.test("receive and display simple notification with message", async () => { + const pyEnv = await startServer(); + addBusServicesToRegistry(); + registry.category("services").add("simple_notification", simpleNotificationService); + await createWebClient({}); + pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "simple_notification", { + message: "simple notification", + }); + await contains(".o_notification_content", { text: "simple notification" }); +}); + +QUnit.test("receive and display simple notification with title", async () => { + const pyEnv = await startServer(); + addBusServicesToRegistry(); + registry.category("services").add("simple_notification", simpleNotificationService); + await createWebClient({}); + pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "simple_notification", { + message: "simple notification", + title: "simple title", + }); + await contains(".o_notification_title", { text: "simple title" }); +}); + +QUnit.test("receive and display simple notification with specific type", async () => { + const pyEnv = await startServer(); + addBusServicesToRegistry(); + registry.category("services").add("simple_notification", simpleNotificationService); + await createWebClient({}); + pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "simple_notification", { + message: "simple notification", + type: "info", + }); + await contains(".o_notification.border-info"); +}); + +QUnit.test("receive and display simple notification as sticky", async () => { + const pyEnv = await startServer(); + addBusServicesToRegistry(); + registry.category("services").add("simple_notification", simpleNotificationService); + await createWebClient({}); + patchWithCleanup(browser, { + setTimeout(fn) { + /** + * Non-sticky notifications are removed after a delay. If thenotification is still + * present when this delay is set to 0 it means it is a sticky one. + */ + return super.setTimeout(fn, 0); + }, + }); + pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "simple_notification", { + message: "simple notification", + sticky: true, + }); + await contains(".o_notification"); +}); diff --git a/static/tests/websocket_worker_tests.js b/static/tests/websocket_worker_tests.js new file mode 100644 index 0000000..46a3862 --- /dev/null +++ b/static/tests/websocket_worker_tests.js @@ -0,0 +1,94 @@ +/** @odoo-module */ + +import { WEBSOCKET_CLOSE_CODES } from "@bus/workers/websocket_worker"; +import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket"; + +import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; + +QUnit.module("Websocket Worker"); + +QUnit.test("connect event is broadcasted after calling start", async function (assert) { + const worker = patchWebsocketWorkerWithCleanup({ + broadcast(type) { + assert.step(`broadcast ${type}`); + }, + }); + worker._start(); + // Wait for the websocket to connect. + await nextTick(); + assert.verifySteps(["broadcast connect"]); +}); + +QUnit.test("disconnect event is broadcasted", async function (assert) { + const worker = patchWebsocketWorkerWithCleanup({ + broadcast(type) { + assert.step(`broadcast ${type}`); + }, + }); + worker._start(); + // Wait for the websocket to connect. + await nextTick(); + worker.websocket.close(WEBSOCKET_CLOSE_CODES.CLEAN); + // Wait for the websocket to disconnect. + await nextTick(); + + assert.verifySteps(["broadcast connect", "broadcast disconnect"]); +}); + +QUnit.test("reconnecting/reconnect event is broadcasted", async function (assert) { + // Patch setTimeout in order for the worker to reconnect immediatly. + patchWithCleanup(window, { + setTimeout: (fn) => fn(), + }); + const worker = patchWebsocketWorkerWithCleanup({ + broadcast(type) { + assert.step(`broadcast ${type}`); + }, + }); + worker._start(); + // Wait for the websocket to connect. + await nextTick(); + worker.websocket.close(WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE); + // Wait for the disconnect/reconnecting/reconnect events. + await nextTick(); + + assert.verifySteps([ + "broadcast connect", + "broadcast disconnect", + "broadcast reconnecting", + "broadcast reconnect", + ]); +}); + +QUnit.test("notification event is broadcasted", async function (assert) { + const notifications = [ + { + id: 70, + message: { + type: "bundle_changed", + payload: { + server_version: "15.5alpha1+e", + }, + }, + }, + ]; + const worker = patchWebsocketWorkerWithCleanup({ + broadcast(type, message) { + if (type === "notification") { + assert.step(`broadcast ${type}`); + assert.deepEqual(message, notifications); + } + }, + }); + worker._start(); + // Wait for the websocket to connect. + await nextTick(); + + worker.websocket.dispatchEvent( + new MessageEvent("message", { + data: JSON.stringify(notifications), + }) + ); + + assert.verifySteps(["broadcast notification"]); +}); diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..ad34fef --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,9 @@ +from . import common +from . import test_assetsbundle +from . import test_health +from . import test_ir_model +from . import test_ir_websocket +from . import test_notify +from . import test_websocket_caryall +from . import test_websocket_controller +from . import test_websocket_rate_limiting diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000..1fe0398 --- /dev/null +++ b/tests/common.py @@ -0,0 +1,139 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import struct +from threading import Event +import unittest +from unittest.mock import patch + +try: + import websocket +except ImportError: + websocket = None + +import odoo.tools +from odoo.tests import HOST, HttpCase +from ..websocket import CloseCode, Websocket, WebsocketConnectionHandler +from ..models.bus import dispatch, hashable, channel_with_db + + +class WebsocketCase(HttpCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + if websocket is None: + cls._logger.warning("websocket-client module is not installed") + raise unittest.SkipTest("websocket-client module is not installed") + cls._WEBSOCKET_URL = f"ws://{HOST}:{odoo.tools.config['http_port']}/websocket" + websocket_allowed_patch = patch.object(WebsocketConnectionHandler, "websocket_allowed", return_value=True) + cls.startClassPatcher(websocket_allowed_patch) + + def setUp(self): + super().setUp() + self._websockets = set() + # Used to ensure websocket connections have been closed + # properly. + self._websocket_events = set() + original_serve_forever = WebsocketConnectionHandler._serve_forever + + def _mocked_serve_forever(*args): + websocket_closed_event = Event() + self._websocket_events.add(websocket_closed_event) + original_serve_forever(*args) + websocket_closed_event.set() + + self._serve_forever_patch = patch.object( + WebsocketConnectionHandler, + '_serve_forever', + wraps=_mocked_serve_forever + ) + self.startPatcher(self._serve_forever_patch) + + def tearDown(self): + self._close_websockets() + super().tearDown() + + def _close_websockets(self): + """ + Close all the connected websockets and wait for the connection + to terminate. + """ + for ws in self._websockets: + if ws.connected: + ws.close(CloseCode.CLEAN) + self.wait_remaining_websocket_connections() + + def websocket_connect(self, *args, **kwargs): + """ + Connect a websocket. If no cookie is given, the connection is + opened with a default session. The created websocket is closed + at the end of the test. + """ + if 'cookie' not in kwargs: + self.session = self.authenticate(None, None) + kwargs['cookie'] = f'session_id={self.session.sid}' + if 'timeout' not in kwargs: + kwargs['timeout'] = 5 + ws = websocket.create_connection( + type(self)._WEBSOCKET_URL, *args, **kwargs + ) + ws.ping() + ws.recv_data_frame(control_frame=True) # pong + self._websockets.add(ws) + return ws + + def subscribe(self, websocket, channels=None, last=None, wait_for_dispatch=True): + """ Subscribe the websocket to the given channels. + + :param websocket: The websocket of the client. + :param channels: The list of channels to subscribe to. + :param last: The last notification id the client received. + :param wait_for_dispatch: Whether to wait for the notification + dispatching trigerred by the subscription. + """ + dispatch_bus_notification_done = Event() + original_dispatch_bus_notifications = Websocket._dispatch_bus_notifications + + def _mocked_dispatch_bus_notifications(self, *args): + original_dispatch_bus_notifications(self, *args) + dispatch_bus_notification_done.set() + + with patch.object(Websocket, '_dispatch_bus_notifications', _mocked_dispatch_bus_notifications): + sub = {'event_name': 'subscribe', 'data': { + 'channels': channels or [], + }} + if last: + sub['data']['last'] = last + websocket.send(json.dumps(sub)) + if wait_for_dispatch: + dispatch_bus_notification_done.wait(timeout=5) + + def trigger_notification_dispatching(self, channels): + """ Notify the websockets subscribed to the given channels that new + notifications are available. Usefull since the bus is not able to do + it during tests. + """ + channels = [ + hashable(channel_with_db(self.registry.db_name, c)) for c in channels + ] + websockets = set() + for channel in channels: + websockets.update(dispatch._channels_to_ws.get(hashable(channel), [])) + for websocket in websockets: + websocket.trigger_notification_dispatching() + + def wait_remaining_websocket_connections(self): + """ Wait for the websocket connections to terminate. """ + for event in self._websocket_events: + event.wait(5) + + def assert_close_with_code(self, websocket, expected_code): + """ + Assert that the websocket is closed with the expected_code. + """ + opcode, payload = websocket.recv_data() + # ensure it's a close frame + self.assertEqual(opcode, 8) + code = struct.unpack('!H', payload[:2])[0] + # ensure the close code is the one we expected + self.assertEqual(code, expected_code) diff --git a/tests/test_assetsbundle.py b/tests/test_assetsbundle.py new file mode 100644 index 0000000..2a5e810 --- /dev/null +++ b/tests/test_assetsbundle.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo.tests +from odoo.osv import expression + + +@odoo.tests.tagged('post_install', '-at_install', 'assets_bundle') +class BusWebTests(odoo.tests.HttpCase): + + def test_bundle_sends_bus(self): + """ + Tests two things: + - Messages are posted to the bus when assets change + i.e. their hash has been recomputed and differ from the attachment's + - The interface deals with those bus messages by displaying one notification + """ + # start from a clean slate + self.env['ir.attachment'].search([('name', 'ilike', 'web.assets_%')]).unlink() + self.env.registry.clear_cache() + + sendones = [] + def patched_sendone(self, channel, notificationType, message): + """ Control API and number of messages posted to the bus linked to + bundle_changed events """ + if notificationType == 'bundle_changed': + sendones.append((channel, message)) + + self.patch(type(self.env['bus.bus']), '_sendone', patched_sendone) + + self.assertEqual(self.url_open('/web/assets/any/web.assets_web.min.js', allow_redirects=False).status_code, 200) + self.assertEqual(self.url_open('/web/assets/any/web.assets_web.min.css', allow_redirects=False).status_code, 200) + self.assertEqual(self.url_open('/web/assets/any/web.assets_backend.min.js', allow_redirects=False).status_code, 200) + self.assertEqual(self.url_open('/web/assets/any/web.assets_backend.min.css', allow_redirects=False).status_code, 200) + + # One sendone for each asset bundle and for each CSS / JS + self.assertEqual( + len(sendones), + 2, + 'Received %s' % '\n'.join('%s - %s' % (tmp[0], tmp[1]) for tmp in sendones) + ) + for (channel, message) in sendones: + self.assertEqual(channel, 'broadcast') + self.assertEqual(len(message), 1) + self.assertTrue(isinstance(message.get('server_version'), str)) diff --git a/tests/test_health.py b/tests/test_health.py new file mode 100644 index 0000000..67f9b31 --- /dev/null +++ b/tests/test_health.py @@ -0,0 +1,12 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import HttpCase + + +class TestBusController(HttpCase): + def test_health(self): + response = self.url_open('/websocket/health') + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertEqual(payload['status'], 'pass') + self.assertFalse(response.cookies.get('session_id')) diff --git a/tests/test_ir_model.py b/tests/test_ir_model.py new file mode 100644 index 0000000..963c558 --- /dev/null +++ b/tests/test_ir_model.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo +from odoo.tests import HttpCase + + +@odoo.tests.tagged('-at_install', 'post_install') +class TestGetModelDefinitions(HttpCase): + def test_access_cr(self): + """ Checks that get_model_definitions does not return anything else than models """ + with self.assertRaises(KeyError): + self.env['ir.model']._get_model_definitions(['res.users', 'cr']) + + def test_access_all_model_fields(self): + """ + Check that get_model_definitions return all the models + and their fields + """ + model_definitions = self.env['ir.model']._get_model_definitions([ + 'res.users', 'res.partner' + ]) + # models are retrieved + self.assertIn('res.users', model_definitions) + self.assertIn('res.partner', model_definitions) + # check that model fields are retrieved + self.assertTrue( + all(fname in model_definitions['res.users'].keys() for fname in ['email', 'name', 'partner_id']) + ) + self.assertTrue( + all(fname in model_definitions['res.partner'].keys() for fname in ['active', 'date', 'name']) + ) + + def test_relational_fields_with_missing_model(self): + """ + Check that get_model_definitions only returns relational fields + if the model is requested + """ + model_definitions = self.env['ir.model']._get_model_definitions([ + 'res.partner' + ]) + # since res.country is not requested, country_id shouldn't be in + # the model definition fields + self.assertNotIn('country_id', model_definitions['res.partner']) + + model_definitions = self.env['ir.model']._get_model_definitions([ + 'res.partner', 'res.country', + ]) + # res.country is requested, country_id should be present on res.partner + self.assertIn('country_id', model_definitions['res.partner']) diff --git a/tests/test_ir_websocket.py b/tests/test_ir_websocket.py new file mode 100644 index 0000000..c6eec9d --- /dev/null +++ b/tests/test_ir_websocket.py @@ -0,0 +1,13 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import common + + +class TestIrWebsocket(common.HttpCase): + def test_only_allow_string_channels_from_frontend(self): + with self.assertRaises(ValueError): + self.env['ir.websocket']._subscribe({ + 'inactivity_period': 1000, + 'last': 0, + 'channels': [('odoo', 'discuss.channel', 5)], + }) diff --git a/tests/test_notify.py b/tests/test_notify.py new file mode 100644 index 0000000..3fd6437 --- /dev/null +++ b/tests/test_notify.py @@ -0,0 +1,49 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import BaseCase + +from ..models.bus import json_dump, get_notify_payloads, NOTIFY_PAYLOAD_MAX_LENGTH + + +class NotifyTests(BaseCase): + + def test_get_notify_payloads(self): + """ + Asserts that the implementation of `get_notify_payloads` + actually splits correctly large payloads + """ + def check_payloads_size(payloads): + for payload in payloads: + self.assertLess(len(payload.encode()), NOTIFY_PAYLOAD_MAX_LENGTH) + + channel = ('dummy_db', 'dummy_model', 12345) + channels = [channel] + self.assertLess(len(json_dump(channels).encode()), NOTIFY_PAYLOAD_MAX_LENGTH) + payloads = get_notify_payloads(channels) + self.assertEqual(len(payloads), 1, + "The payload is less then the threshold, " + "there should be 1 payload only, as it shouldn't be split") + channels = [channel] * 100 + self.assertLess(len(json_dump(channels).encode()), NOTIFY_PAYLOAD_MAX_LENGTH) + payloads = get_notify_payloads(channels) + self.assertEqual(len(payloads), 1, + "The payload is less then the threshold, " + "there should be 1 payload only, as it shouldn't be split") + check_payloads_size(payloads) + channels = [channel] * 1000 + self.assertGreaterEqual(len(json_dump(channels).encode()), NOTIFY_PAYLOAD_MAX_LENGTH) + payloads = get_notify_payloads(channels) + self.assertGreater(len(payloads), 1, + "Payload was larger than the threshold, it should've been split") + check_payloads_size(payloads) + + fat_channel = tuple(item * 1000 for item in channel) + channels = [fat_channel] + self.assertEqual(len(channels), 1, "There should be only 1 channel") + self.assertGreaterEqual(len(json_dump(channels).encode()), NOTIFY_PAYLOAD_MAX_LENGTH) + payloads = get_notify_payloads(channels) + self.assertEqual(len(payloads), 1, + "Payload was larger than the threshold, but shouldn't be split, " + "as it contains only 1 channel") + with self.assertRaises(AssertionError): + check_payloads_size(payloads) diff --git a/tests/test_websocket_caryall.py b/tests/test_websocket_caryall.py new file mode 100644 index 0000000..f9d5f15 --- /dev/null +++ b/tests/test_websocket_caryall.py @@ -0,0 +1,258 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import gc +import json +import os +from collections import defaultdict +from datetime import timedelta +from freezegun import freeze_time +from threading import Event +from unittest.mock import patch +from weakref import WeakSet +try: + from websocket._exceptions import WebSocketBadStatusException +except ImportError: + pass + +from odoo.api import Environment +from odoo.tests import common, new_test_user +from .common import WebsocketCase +from .. import websocket as websocket_module +from ..models.bus import dispatch +from ..models.ir_websocket import IrWebsocket +from ..websocket import ( + CloseCode, + Frame, + Opcode, + TimeoutManager, + TimeoutReason, + Websocket, + WebsocketConnectionHandler, +) + +@common.tagged('post_install', '-at_install') +class TestWebsocketCaryall(WebsocketCase): + def test_lifecycle_hooks(self): + events = [] + with patch.object(Websocket, '_Websocket__event_callbacks', defaultdict(set)): + @Websocket.onopen + def onopen(env, websocket): # pylint: disable=unused-variable + self.assertIsInstance(env, Environment) + self.assertIsInstance(websocket, Websocket) + events.append('open') + + @Websocket.onclose + def onclose(env, websocket): # pylint: disable=unused-variable + self.assertIsInstance(env, Environment) + self.assertIsInstance(websocket, Websocket) + events.append('close') + + ws = self.websocket_connect() + ws.close(CloseCode.CLEAN) + self.wait_remaining_websocket_connections() + self.assertEqual(events, ['open', 'close']) + + def test_instances_weak_set(self): + with patch.object(websocket_module, "_websocket_instances", WeakSet()): + first_ws = self.websocket_connect() + second_ws = self.websocket_connect() + self.assertEqual(len(websocket_module._websocket_instances), 2) + first_ws.close(CloseCode.CLEAN) + second_ws.close(CloseCode.CLEAN) + self.wait_remaining_websocket_connections() + # serve_forever_patch prevent websocket instances from being + # collected. Stop it now. + self._serve_forever_patch.stop() + gc.collect() + self.assertEqual(len(websocket_module._websocket_instances), 0) + + def test_timeout_manager_no_response_timeout(self): + with freeze_time('2022-08-19') as frozen_time: + timeout_manager = TimeoutManager() + # A PING frame was just sent, if no pong has been received + # within TIMEOUT seconds, the connection should have timed out. + timeout_manager.acknowledge_frame_sent(Frame(Opcode.PING)) + self.assertEqual(timeout_manager._awaited_opcode, Opcode.PONG) + frozen_time.tick(delta=timedelta(seconds=TimeoutManager.TIMEOUT / 2)) + self.assertFalse(timeout_manager.has_timed_out()) + frozen_time.tick(delta=timedelta(seconds=TimeoutManager.TIMEOUT / 2)) + self.assertTrue(timeout_manager.has_timed_out()) + self.assertEqual(timeout_manager.timeout_reason, TimeoutReason.NO_RESPONSE) + + timeout_manager = TimeoutManager() + # A CLOSE frame was just sent, if no close has been received + # within TIMEOUT seconds, the connection should have timed out. + timeout_manager.acknowledge_frame_sent(Frame(Opcode.CLOSE)) + self.assertEqual(timeout_manager._awaited_opcode, Opcode.CLOSE) + frozen_time.tick(delta=timedelta(seconds=TimeoutManager.TIMEOUT / 2)) + self.assertFalse(timeout_manager.has_timed_out()) + frozen_time.tick(delta=timedelta(seconds=TimeoutManager.TIMEOUT / 2)) + self.assertTrue(timeout_manager.has_timed_out()) + self.assertEqual(timeout_manager.timeout_reason, TimeoutReason.NO_RESPONSE) + + def test_timeout_manager_keep_alive_timeout(self): + with freeze_time('2022-08-19') as frozen_time: + timeout_manager = TimeoutManager() + frozen_time.tick(delta=timedelta(seconds=timeout_manager._keep_alive_timeout / 2)) + self.assertFalse(timeout_manager.has_timed_out()) + frozen_time.tick(delta=timedelta(seconds=timeout_manager._keep_alive_timeout / 2 + 1)) + self.assertTrue(timeout_manager.has_timed_out()) + self.assertEqual(timeout_manager.timeout_reason, TimeoutReason.KEEP_ALIVE) + + def test_timeout_manager_reset_wait_for(self): + timeout_manager = TimeoutManager() + # PING frame + timeout_manager.acknowledge_frame_sent(Frame(Opcode.PING)) + self.assertEqual(timeout_manager._awaited_opcode, Opcode.PONG) + timeout_manager.acknowledge_frame_receipt(Frame(Opcode.PONG)) + self.assertIsNone(timeout_manager._awaited_opcode) + + # CLOSE frame + timeout_manager.acknowledge_frame_sent(Frame(Opcode.CLOSE)) + self.assertEqual(timeout_manager._awaited_opcode, Opcode.CLOSE) + timeout_manager.acknowledge_frame_receipt(Frame(Opcode.CLOSE)) + self.assertIsNone(timeout_manager._awaited_opcode) + + def test_user_login(self): + websocket = self.websocket_connect() + new_test_user(self.env, login='test_user', password='Password!1') + self.authenticate('test_user', 'Password!1') + # The session with whom the websocket connected has been + # deleted. WebSocket should disconnect in order for the + # session to be updated. + self.subscribe(websocket, wait_for_dispatch=False) + self.assert_close_with_code(websocket, CloseCode.SESSION_EXPIRED) + + def test_user_logout_incoming_message(self): + new_test_user(self.env, login='test_user', password='Password!1') + user_session = self.authenticate('test_user', 'Password!1') + websocket = self.websocket_connect(cookie=f'session_id={user_session.sid};') + self.url_open('/web/session/logout') + # The session with whom the websocket connected has been + # deleted. WebSocket should disconnect in order for the + # session to be updated. + self.subscribe(websocket, wait_for_dispatch=False) + self.assert_close_with_code(websocket, CloseCode.SESSION_EXPIRED) + + def test_user_logout_outgoing_message(self): + new_test_user(self.env, login='test_user', password='Password!1') + user_session = self.authenticate('test_user', 'Password!1') + websocket = self.websocket_connect(cookie=f'session_id={user_session.sid};') + self.subscribe(websocket, ['channel1'], self.env['bus.bus']._bus_last_id()) + self.url_open('/web/session/logout') + # Simulate postgres notify. The session with whom the websocket + # connected has been deleted. WebSocket should be closed without + # receiving the message. + self.env['bus.bus']._sendone('channel1', 'notif type', 'message') + self.trigger_notification_dispatching(["channel1"]) + self.assert_close_with_code(websocket, CloseCode.SESSION_EXPIRED) + + def test_channel_subscription_disconnect(self): + websocket = self.websocket_connect() + self.subscribe(websocket, ['my_channel'], self.env['bus.bus']._bus_last_id()) + # channel is added as expected to the channel to websocket map. + self.assertIn((self.env.registry.db_name, 'my_channel'), dispatch._channels_to_ws) + websocket.close(CloseCode.CLEAN) + self.wait_remaining_websocket_connections() + # channel is removed as expected when removing the last + # websocket that was listening to this channel. + self.assertNotIn((self.env.registry.db_name, 'my_channel'), dispatch._channels_to_ws) + + def test_channel_subscription_update(self): + websocket = self.websocket_connect() + self.subscribe(websocket, ['my_channel'], self.env['bus.bus']._bus_last_id()) + # channel is added as expected to the channel to websocket map. + self.assertIn((self.env.registry.db_name, 'my_channel'), dispatch._channels_to_ws) + self.subscribe(websocket, ['my_channel_2'], self.env['bus.bus']._bus_last_id()) + # channel is removed as expected when updating the subscription. + self.assertNotIn((self.env.registry.db_name, 'my_channel'), dispatch._channels_to_ws) + + def test_trigger_notification(self): + websocket = self.websocket_connect() + self.subscribe(websocket, ['my_channel'], self.env['bus.bus']._bus_last_id()) + self.env['bus.bus']._sendone('my_channel', 'notif_type', 'message') + self.trigger_notification_dispatching(["my_channel"]) + notifications = json.loads(websocket.recv()) + self.assertEqual(1, len(notifications)) + self.assertEqual(notifications[0]['message']['type'], 'notif_type') + self.assertEqual(notifications[0]['message']['payload'], 'message') + self.env['bus.bus']._sendone('my_channel', 'notif_type', 'another_message') + self.trigger_notification_dispatching(["my_channel"]) + notifications = json.loads(websocket.recv()) + # First notification has been received, we should only receive + # the second one. + self.assertEqual(1, len(notifications)) + self.assertEqual(notifications[0]['message']['type'], 'notif_type') + self.assertEqual(notifications[0]['message']['payload'], 'another_message') + + def test_subscribe_higher_last_notification_id(self): + server_last_notification_id = self.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0 + client_last_notification_id = server_last_notification_id + 1 + + with patch.object(Websocket, 'subscribe', side_effect=Websocket.subscribe, autospec=True) as mock: + websocket = self.websocket_connect() + self.subscribe(websocket, ['my_channel'], client_last_notification_id) + self.assertEqual(mock.call_args[0][2], 0) + + def test_subscribe_lower_last_notification_id(self): + server_last_notification_id = self.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0 + client_last_notification_id = server_last_notification_id - 1 + + with patch.object(Websocket, 'subscribe', side_effect=Websocket.subscribe, autospec=True) as mock: + websocket = self.websocket_connect() + self.subscribe(websocket, ['my_channel'], client_last_notification_id) + self.assertEqual(mock.call_args[0][2], client_last_notification_id) + + def test_subscribe_to_custom_channel(self): + channel = self.env["res.partner"].create({"name": "John"}) + websocket = self.websocket_connect() + with patch.object(IrWebsocket, "_build_bus_channel_list", return_value=[channel]): + self.subscribe(websocket, [], self.env['bus.bus']._bus_last_id()) + self.env["bus.bus"]._sendmany([ + (channel, "notif_on_global_channel", "message"), + ((channel, "PRIVATE"), "notif_on_private_channel", "message"), + ]) + self.trigger_notification_dispatching([channel, (channel, "PRIVATE")]) + notifications = json.loads(websocket.recv()) + self.assertEqual(len(notifications), 1) + self.assertEqual(notifications[0]['message']['type'], 'notif_on_global_channel') + self.assertEqual(notifications[0]['message']['payload'], 'message') + + with patch.object(IrWebsocket, "_build_bus_channel_list", return_value=[(channel, "PRIVATE")]): + self.subscribe(websocket, [], self.env['bus.bus']._bus_last_id()) + self.env["bus.bus"]._sendmany([ + (channel, "notif_on_global_channel", "message"), + ((channel, "PRIVATE"), "notif_on_private_channel", "message"), + ]) + self.trigger_notification_dispatching([channel, (channel, "PRIVATE")]) + notifications = json.loads(websocket.recv()) + self.assertEqual(len(notifications), 1) + self.assertEqual(notifications[0]['message']['type'], 'notif_on_private_channel') + self.assertEqual(notifications[0]['message']['payload'], 'message') + + def test_no_cursor_when_no_callback_for_lifecycle_event(self): + with patch.object(Websocket, '_Websocket__event_callbacks', defaultdict(set)): + with patch('odoo.addons.bus.websocket.acquire_cursor') as mock: + self.websocket_connect() + self.assertFalse(mock.called) + + @patch.dict(os.environ, {"ODOO_BUS_PUBLIC_SAMESITE_WS": "True"}) + def test_public_configuration(self): + new_test_user(self.env, login='test_user', password='Password!1') + user_session = self.authenticate('test_user', 'Password!1') + serve_forever_called_event = Event() + original_serve_forever = WebsocketConnectionHandler._serve_forever + + def serve_forever(websocket, *args): + original_serve_forever(websocket, *args) + self.assertNotEqual(websocket._session.sid, user_session.sid) + self.assertNotEqual(websocket._session.uid, user_session.uid) + serve_forever_called_event.set() + + with patch.object(WebsocketConnectionHandler, '_serve_forever', side_effect=serve_forever) as mock: + self.websocket_connect( + cookie=f'session_id={user_session.sid};', + origin="http://example.com" + ) + serve_forever_called_event.wait(timeout=5) + self.assertTrue(mock.called) diff --git a/tests/test_websocket_controller.py b/tests/test_websocket_controller.py new file mode 100644 index 0000000..0ff5297 --- /dev/null +++ b/tests/test_websocket_controller.py @@ -0,0 +1,71 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json + +from odoo.tests import JsonRpcException +from odoo.addons.base.tests.common import HttpCaseWithUserDemo + + +class TestWebsocketController(HttpCaseWithUserDemo): + def test_websocket_peek(self): + result = self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': True, + }) + + # Response containing channels/notifications is retrieved and is + # conform to excpectations. + self.assertIsNotNone(result) + channels = result.get('channels') + self.assertIsNotNone(channels) + self.assertIsInstance(channels, list) + notifications = result.get('notifications') + self.assertIsNotNone(notifications) + self.assertIsInstance(notifications, list) + + result = self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': False, + }) + + # Reponse is received as long as the session is valid. + self.assertIsNotNone(result) + + def test_websocket_peek_session_expired_login(self): + session = self.authenticate(None, None) + # first rpc should be fine + self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': True, + }) + + self.authenticate('admin', 'admin') + # rpc with outdated session should lead to error. + headers = {'Cookie': f'session_id={session.sid};'} + with self.assertRaises(JsonRpcException, msg='odoo.http.SessionExpiredException'): + self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': False, + }, headers=headers) + + def test_websocket_peek_session_expired_logout(self): + session = self.authenticate('demo', 'demo') + # first rpc should be fine + self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': True, + }) + self.url_open('/web/session/logout') + # rpc with outdated session should lead to error. + headers = {'Cookie': f'session_id={session.sid};'} + with self.assertRaises(JsonRpcException, msg='odoo.http.SessionExpiredException'): + self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': False, + }, headers=headers) diff --git a/tests/test_websocket_rate_limiting.py b/tests/test_websocket_rate_limiting.py new file mode 100644 index 0000000..b1063d4 --- /dev/null +++ b/tests/test_websocket_rate_limiting.py @@ -0,0 +1,66 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import time + +try: + from websocket._exceptions import WebSocketProtocolException +except ImportError: + pass + +from odoo.tests import common +from .common import WebsocketCase +from ..websocket import CloseCode, Websocket + +@common.tagged('post_install', '-at_install') +class TestWebsocketRateLimiting(WebsocketCase): + def test_rate_limiting_base_ok(self): + ws = self.websocket_connect() + + for _ in range(Websocket.RL_BURST + 1): + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + time.sleep(Websocket.RL_DELAY) + + def test_rate_limiting_base_ko(self): + ws = self.websocket_connect() + + # Websocket client's close codes are not up to date. Indeed, the + # 1013 close code results in a protocol exception while it is a + # valid, registered close code ("TRY LATER") : + # https://www.iana.org/assignments/websocket/websocket.xhtml + with self.assertRaises(WebSocketProtocolException) as cm: + for _ in range(Websocket.RL_BURST + 1): + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + self.assert_close_with_code(ws, CloseCode.TRY_LATER) + self.assertEqual(str(cm.exception), 'Invalid close opcode.') + + def test_rate_limiting_opening_burst(self): + ws = self.websocket_connect() + + # first RL_BURST requests are accepted. + for _ in range(Websocket.RL_BURST): + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + + # sending at a correct rate after burst should be accepted. + for _ in range(2): + time.sleep(Websocket.RL_DELAY) + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + + def test_rate_limiting_start_ok_end_ko(self): + ws = self.websocket_connect() + + # first requests are legit and should be accepted + for _ in range(Websocket.RL_BURST + 1): + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + time.sleep(Websocket.RL_DELAY) + + # Websocket client's close codes are not up to date. Indeed, the + # 1013 close code results in a protocol exception while it is a + # valid, registered close code ("TRY LATER") : + # https://www.iana.org/assignments/websocket/websocket.xhtml + with self.assertRaises(WebSocketProtocolException) as cm: + # those requests are illicit and should not be accepted. + for _ in range(Websocket.RL_BURST * 2): + ws.send(json.dumps({'event_name': 'test_rate_limiting'})) + self.assert_close_with_code(ws, CloseCode.TRY_LATER) + self.assertEqual(str(cm.exception), 'Invalid close opcode.') diff --git a/websocket.py b/websocket.py new file mode 100644 index 0000000..e2c899d --- /dev/null +++ b/websocket.py @@ -0,0 +1,949 @@ +import base64 +import functools +import hashlib +import json +import logging +import os +import psycopg2 +import random +import socket +import struct +import selectors +import threading +import time +from collections import defaultdict, deque +from contextlib import closing, suppress +from enum import IntEnum +from psycopg2.pool import PoolError +from urllib.parse import urlparse +from weakref import WeakSet + +from werkzeug.local import LocalStack +from werkzeug.exceptions import BadRequest, HTTPException, ServiceUnavailable + +import odoo +from odoo import api +from .models.bus import dispatch +from odoo.http import root, Request, Response, SessionExpiredException, get_default_session +from odoo.modules.registry import Registry +from odoo.service import model as service_model +from odoo.service.server import CommonServer +from odoo.service.security import check_session +from odoo.tools import config + +_logger = logging.getLogger(__name__) + + +MAX_TRY_ON_POOL_ERROR = 10 +DELAY_ON_POOL_ERROR = 0.03 + + +def acquire_cursor(db): + """ Try to acquire a cursor up to `MAX_TRY_ON_POOL_ERROR` """ + for tryno in range(1, MAX_TRY_ON_POOL_ERROR + 1): + with suppress(PoolError): + return odoo.registry(db).cursor() + time.sleep(random.uniform(DELAY_ON_POOL_ERROR, DELAY_ON_POOL_ERROR * tryno)) + raise PoolError('Failed to acquire cursor after %s retries' % MAX_TRY_ON_POOL_ERROR) + + +# ------------------------------------------------------ +# EXCEPTIONS +# ------------------------------------------------------ + +class UpgradeRequired(HTTPException): + code = 426 + description = "Wrong websocket version was given during the handshake" + + def get_headers(self, environ=None): + headers = super().get_headers(environ) + headers.append(( + 'Sec-WebSocket-Version', + '; '.join(WebsocketConnectionHandler.SUPPORTED_VERSIONS) + )) + return headers + + +class WebsocketException(Exception): + """ Base class for all websockets exceptions """ + + +class ConnectionClosed(WebsocketException): + """ + Raised when the other end closes the socket without performing + the closing handshake. + """ + + +class InvalidCloseCodeException(WebsocketException): + def __init__(self, code): + super().__init__(f"Invalid close code: {code}") + + +class InvalidDatabaseException(WebsocketException): + """ + When raised: the database probably does not exists anymore, the + database is corrupted or the database version doesn't match the + server version. + """ + + +class InvalidStateException(WebsocketException): + """ + Raised when an operation is forbidden in the current state. + """ + + +class InvalidWebsocketRequest(WebsocketException): + """ + Raised when a websocket request is invalid (format, wrong args). + """ + + +class PayloadTooLargeException(WebsocketException): + """ + Raised when a websocket message is too large. + """ + + +class ProtocolError(WebsocketException): + """ + Raised when a frame format doesn't match expectations. + """ + + +class RateLimitExceededException(Exception): + """ + Raised when a client exceeds the number of request in a given + time. + """ + + +# ------------------------------------------------------ +# WEBSOCKET LIFECYCLE +# ------------------------------------------------------ + + +class LifecycleEvent(IntEnum): + OPEN = 0 + CLOSE = 1 + + +# ------------------------------------------------------ +# WEBSOCKET +# ------------------------------------------------------ + + +class Opcode(IntEnum): + CONTINUE = 0x00 + TEXT = 0x01 + BINARY = 0x02 + CLOSE = 0x08 + PING = 0x09 + PONG = 0x0A + + +class CloseCode(IntEnum): + CLEAN = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + INCORRECT_DATA = 1003 + ABNORMAL_CLOSURE = 1006 + INCONSISTENT_DATA = 1007 + MESSAGE_VIOLATING_POLICY = 1008 + MESSAGE_TOO_BIG = 1009 + EXTENSION_NEGOTIATION_FAILED = 1010 + SERVER_ERROR = 1011 + RESTART = 1012 + TRY_LATER = 1013 + BAD_GATEWAY = 1014 + SESSION_EXPIRED = 4001 + KEEP_ALIVE_TIMEOUT = 4002 + + +class ConnectionState(IntEnum): + OPEN = 0 + CLOSING = 1 + CLOSED = 2 + + +DATA_OP = {Opcode.TEXT, Opcode.BINARY} +CTRL_OP = {Opcode.CLOSE, Opcode.PING, Opcode.PONG} +HEARTBEAT_OP = {Opcode.PING, Opcode.PONG} + +VALID_CLOSE_CODES = { + code for code in CloseCode if code is not CloseCode.ABNORMAL_CLOSURE +} +CLEAN_CLOSE_CODES = {CloseCode.CLEAN, CloseCode.GOING_AWAY, CloseCode.RESTART} +RESERVED_CLOSE_CODES = range(3000, 5000) + +_XOR_TABLE = [bytes(a ^ b for a in range(256)) for b in range(256)] + + +class Frame: + def __init__( + self, + opcode, + payload=b'', + fin=True, + rsv1=False, + rsv2=False, + rsv3=False + ): + self.opcode = opcode + self.payload = payload + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + + +class CloseFrame(Frame): + def __init__(self, code, reason): + if code not in VALID_CLOSE_CODES and code not in RESERVED_CLOSE_CODES: + raise InvalidCloseCodeException(code) + payload = struct.pack('!H', code) + if reason: + payload += reason.encode('utf-8') + self.code = code + self.reason = reason + super().__init__(Opcode.CLOSE, payload) + + +_websocket_instances = WeakSet() + + +class Websocket: + __event_callbacks = defaultdict(set) + # Maximum size for a message in bytes, whether it is sent as one + # frame or many fragmented ones. + MESSAGE_MAX_SIZE = 2 ** 20 + # Proxies usually close a connection after 1 minute of inactivity. + # Therefore, a PING frame have to be sent if no frame is either sent + # or received within CONNECTION_TIMEOUT - 15 seconds. + CONNECTION_TIMEOUT = 60 + INACTIVITY_TIMEOUT = CONNECTION_TIMEOUT - 15 + # How many requests can be made in excess of the given rate. + RL_BURST = int(config['websocket_rate_limit_burst']) + # How many seconds between each request. + RL_DELAY = float(config['websocket_rate_limit_delay']) + + def __init__(self, sock, session): + # Session linked to the current websocket connection. + self._session = session + self._db = session.db + self.__socket = sock + self._close_sent = False + self._close_received = False + self._timeout_manager = TimeoutManager() + # Used for rate limiting. + self._incoming_frame_timestamps = deque(maxlen=self.RL_BURST) + # Used to notify the websocket that bus notifications are + # available. + self.__notif_sock_w, self.__notif_sock_r = socket.socketpair() + self._channels = set() + self._last_notif_sent_id = 0 + # Websocket start up + self.__selector = ( + selectors.PollSelector() + if odoo.evented and hasattr(selectors, 'PollSelector') + else selectors.DefaultSelector() + ) + self.__selector.register(self.__socket, selectors.EVENT_READ) + self.__selector.register(self.__notif_sock_r, selectors.EVENT_READ) + self.state = ConnectionState.OPEN + _websocket_instances.add(self) + self._trigger_lifecycle_event(LifecycleEvent.OPEN) + + # ------------------------------------------------------ + # PUBLIC METHODS + # ------------------------------------------------------ + + def get_messages(self): + while self.state is not ConnectionState.CLOSED: + try: + readables = { + selector_key[0].fileobj for selector_key in + self.__selector.select(self.INACTIVITY_TIMEOUT) + } + if self._timeout_manager.has_timed_out() and self.state is ConnectionState.OPEN: + self.disconnect( + CloseCode.ABNORMAL_CLOSURE + if self._timeout_manager.timeout_reason is TimeoutReason.NO_RESPONSE + else CloseCode.KEEP_ALIVE_TIMEOUT + ) + continue + if not readables: + self._send_ping_frame() + continue + if self.__notif_sock_r in readables: + self._dispatch_bus_notifications() + if self.__socket in readables: + message = self._process_next_message() + if message is not None: + yield message + except Exception as exc: + self._handle_transport_error(exc) + + def disconnect(self, code, reason=None): + """ + Initiate the closing handshake that is, send a close frame + to the other end which will then send us back an + acknowledgment. Upon the reception of this acknowledgment, + the `_terminate` method will be called to perform an + orderly shutdown. Note that we don't need to wait for the + acknowledgment if the connection was failed beforewards. + """ + if code is not CloseCode.ABNORMAL_CLOSURE: + self._send_close_frame(code, reason) + else: + self._terminate() + + @classmethod + def onopen(cls, func): + cls.__event_callbacks[LifecycleEvent.OPEN].add(func) + return func + + @classmethod + def onclose(cls, func): + cls.__event_callbacks[LifecycleEvent.CLOSE].add(func) + return func + + def subscribe(self, channels, last): + """ Subscribe to bus channels. """ + self._channels = channels + if self._last_notif_sent_id < last: + self._last_notif_sent_id = last + # Dispatch past notifications if there are any. + self.trigger_notification_dispatching() + + def trigger_notification_dispatching(self): + """ + Warn the socket that notifications are available. Ignore if a + dispatch is already planned or if the socket is already in the + closing state. + """ + if self.state is not ConnectionState.OPEN: + return + readables = { + selector_key[0].fileobj for selector_key in + self.__selector.select(0) + } + if self.__notif_sock_r not in readables: + # Send a random bit to mark the socket as readable. + self.__notif_sock_w.send(b'x') + + # ------------------------------------------------------ + # PRIVATE METHODS + # ------------------------------------------------------ + + def _get_next_frame(self): + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-------+-+-------------+-------------------------------+ + # |F|R|R|R| opcode|M| Payload len | Extended payload length | + # |I|S|S|S| (4) |A| (7) | (16/64) | + # |N|V|V|V| |S| | (if payload len==126/127) | + # | |1|2|3| |K| | | + # +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + # | Extended payload length continued, if payload len == 127 | + # + - - - - - - - - - - - - - - - +-------------------------------+ + # | |Masking-key, if MASK set to 1 | + # +-------------------------------+-------------------------------+ + # | Masking-key (continued) | Payload Data | + # +-------------------------------- - - - - - - - - - - - - - - - + + # : Payload Data continued ... : + # + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + # | Payload Data continued ... | + # +---------------------------------------------------------------+ + def recv_bytes(n): + """ Pull n bytes from the socket """ + data = bytearray() + while len(data) < n: + received_data = self.__socket.recv(n - len(data)) + if not received_data: + raise ConnectionClosed() + data.extend(received_data) + return data + + def is_bit_set(byte, n): + """ + Check whether nth bit of byte is set or not (from left + to right). + """ + return byte & (1 << (7 - n)) + + def apply_mask(payload, mask): + # see: https://www.willmcgugan.com/blog/tech/post/speeding-up-websockets-60x/ + a, b, c, d = (_XOR_TABLE[n] for n in mask) + payload[::4] = payload[::4].translate(a) + payload[1::4] = payload[1::4].translate(b) + payload[2::4] = payload[2::4].translate(c) + payload[3::4] = payload[3::4].translate(d) + return payload + + self._limit_rate() + first_byte, second_byte = recv_bytes(2) + fin, rsv1, rsv2, rsv3 = (is_bit_set(first_byte, n) for n in range(4)) + try: + opcode = Opcode(first_byte & 0b00001111) + except ValueError as exc: + raise ProtocolError(exc) + payload_length = second_byte & 0b01111111 + + if rsv1 or rsv2 or rsv3: + raise ProtocolError("Reserved bits must be unset") + if not is_bit_set(second_byte, 0): + raise ProtocolError("Frame must be masked") + if opcode in CTRL_OP: + if not fin: + raise ProtocolError("Control frames cannot be fragmented") + if payload_length > 125: + raise ProtocolError( + "Control frames payload must be smaller than 126" + ) + if payload_length == 126: + payload_length = struct.unpack('!H', recv_bytes(2))[0] + elif payload_length == 127: + payload_length = struct.unpack('!Q', recv_bytes(8))[0] + if payload_length > self.MESSAGE_MAX_SIZE: + raise PayloadTooLargeException() + + mask = recv_bytes(4) + payload = apply_mask(recv_bytes(payload_length), mask) + frame = Frame(opcode, bytes(payload), fin, rsv1, rsv2, rsv3) + self._timeout_manager.acknowledge_frame_receipt(frame) + return frame + + def _process_next_message(self): + """ + Process the next message coming throught the socket. If a + data message can be extracted, return its decoded payload. + As per the RFC, only control frames will be processed once + the connection reaches the closing state. + """ + frame = self._get_next_frame() + if frame.opcode in CTRL_OP: + self._handle_control_frame(frame) + return + if self.state is not ConnectionState.OPEN: + # After receiving a control frame indicating the connection + # should be closed, a peer discards any further data + # received. + return + if frame.opcode is Opcode.CONTINUE: + raise ProtocolError("Unexpected continuation frame") + message = frame.payload + if not frame.fin: + message = self._recover_fragmented_message(frame) + return ( + message.decode('utf-8') + if message is not None and frame.opcode is Opcode.TEXT else message + ) + + def _recover_fragmented_message(self, initial_frame): + message_fragments = bytearray(initial_frame.payload) + while True: + frame = self._get_next_frame() + if frame.opcode in CTRL_OP: + # Control frames can be received in the middle of a + # fragmented message, process them as soon as possible. + self._handle_control_frame(frame) + if self.state is not ConnectionState.OPEN: + return + continue + if frame.opcode is not Opcode.CONTINUE: + raise ProtocolError("A continuation frame was expected") + message_fragments.extend(frame.payload) + if len(message_fragments) > self.MESSAGE_MAX_SIZE: + raise PayloadTooLargeException() + if frame.fin: + return bytes(message_fragments) + + def _send(self, message): + if self.state is not ConnectionState.OPEN: + raise InvalidStateException( + "Trying to send a frame on a closed socket" + ) + opcode = Opcode.BINARY + if not isinstance(message, (bytes, bytearray)): + opcode = Opcode.TEXT + self._send_frame(Frame(opcode, message)) + + def _send_frame(self, frame): + if frame.opcode in CTRL_OP and len(frame.payload) > 125: + raise ProtocolError( + "Control frames should have a payload length smaller than 126" + ) + if isinstance(frame.payload, str): + frame.payload = frame.payload.encode('utf-8') + elif not isinstance(frame.payload, (bytes, bytearray)): + frame.payload = json.dumps(frame.payload).encode('utf-8') + + output = bytearray() + first_byte = ( + (0b10000000 if frame.fin else 0) + | (0b01000000 if frame.rsv1 else 0) + | (0b00100000 if frame.rsv2 else 0) + | (0b00010000 if frame.rsv3 else 0) + | frame.opcode + ) + payload_length = len(frame.payload) + if payload_length < 126: + output.extend( + struct.pack('!BB', first_byte, payload_length) + ) + elif payload_length < 65536: + output.extend( + struct.pack('!BBH', first_byte, 126, payload_length) + ) + else: + output.extend( + struct.pack('!BBQ', first_byte, 127, payload_length) + ) + output.extend(frame.payload) + self.__socket.sendall(output) + self._timeout_manager.acknowledge_frame_sent(frame) + if not isinstance(frame, CloseFrame): + return + self.state = ConnectionState.CLOSING + self._close_sent = True + if frame.code not in CLEAN_CLOSE_CODES or self._close_received: + return self._terminate() + # After sending a control frame indicating the connection + # should be closed, a peer does not send any further data. + self.__selector.unregister(self.__notif_sock_r) + + def _send_close_frame(self, code, reason=None): + """ Send a close frame. """ + self._send_frame(CloseFrame(code, reason)) + + def _send_ping_frame(self): + """ Send a ping frame """ + self._send_frame(Frame(Opcode.PING)) + + def _send_pong_frame(self, payload): + """ Send a pong frame """ + self._send_frame(Frame(Opcode.PONG, payload)) + + def _terminate(self): + """ Close the underlying TCP socket. """ + with suppress(OSError, TimeoutError): + self.__socket.shutdown(socket.SHUT_WR) + # Call recv until obtaining a return value of 0 indicating + # the other end has performed an orderly shutdown. A timeout + # is set to ensure the connection will be closed even if + # the other end does not close the socket properly. + self.__socket.settimeout(1) + while self.__socket.recv(4096): + pass + self.__selector.unregister(self.__socket) + self.__selector.close() + self.__socket.close() + self.state = ConnectionState.CLOSED + dispatch.unsubscribe(self) + self._trigger_lifecycle_event(LifecycleEvent.CLOSE) + + def _handle_control_frame(self, frame): + if frame.opcode is Opcode.PING: + self._send_pong_frame(frame.payload) + elif frame.opcode is Opcode.CLOSE: + self.state = ConnectionState.CLOSING + self._close_received = True + code, reason = CloseCode.CLEAN, None + if len(frame.payload) >= 2: + code = struct.unpack('!H', frame.payload[:2])[0] + reason = frame.payload[2:].decode('utf-8') + elif frame.payload: + raise ProtocolError("Malformed closing frame") + if not self._close_sent: + self._send_close_frame(code, reason) + else: + self._terminate() + + def _handle_transport_error(self, exc): + """ + Find out which close code should be sent according to given + exception and call `self.disconnect` in order to close the + connection cleanly. + """ + code, reason = CloseCode.SERVER_ERROR, str(exc) + if isinstance(exc, (ConnectionClosed, OSError)): + code = CloseCode.ABNORMAL_CLOSURE + elif isinstance(exc, (ProtocolError, InvalidCloseCodeException)): + code = CloseCode.PROTOCOL_ERROR + elif isinstance(exc, UnicodeDecodeError): + code = CloseCode.INCONSISTENT_DATA + elif isinstance(exc, PayloadTooLargeException): + code = CloseCode.MESSAGE_TOO_BIG + elif isinstance(exc, (PoolError, RateLimitExceededException)): + code = CloseCode.TRY_LATER + elif isinstance(exc, SessionExpiredException): + code = CloseCode.SESSION_EXPIRED + if code is CloseCode.SERVER_ERROR: + reason = None + registry = Registry(self._session.db) + sequence = registry.registry_sequence + registry = registry.check_signaling() + if sequence != registry.registry_sequence: + _logger.warning("Bus operation aborted; registry has been reloaded") + else: + _logger.error(exc, exc_info=True) + self.disconnect(code, reason) + + def _limit_rate(self): + """ + This method is a simple rate limiter designed not to allow + more than one request by `RL_DELAY` seconds. `RL_BURST` specify + how many requests can be made in excess of the given rate at the + begining. When requests are received too fast, raises the + `RateLimitExceededException`. + """ + now = time.time() + if len(self._incoming_frame_timestamps) >= self.RL_BURST: + elapsed_time = now - self._incoming_frame_timestamps[0] + if elapsed_time < self.RL_DELAY * self.RL_BURST: + raise RateLimitExceededException() + self._incoming_frame_timestamps.append(now) + + def _trigger_lifecycle_event(self, event_type): + """ + Trigger a lifecycle event that is, call every function + registered for this event type. Every callback is given both the + environment and the related websocket. + """ + if not self.__event_callbacks[event_type]: + return + with closing(acquire_cursor(self._db)) as cr: + env = api.Environment(cr, self._session.uid, self._session.context) + for callback in self.__event_callbacks[event_type]: + try: + service_model.retrying(functools.partial(callback, env, self), env) + except Exception: + _logger.warning( + 'Error during Websocket %s callback', + LifecycleEvent(event_type).name, + exc_info=True + ) + + def _dispatch_bus_notifications(self): + """ + Dispatch notifications related to the registered channels. If + the session is expired, close the connection with the + `SESSION_EXPIRED` close code. If no cursor can be acquired, + close the connection with the `TRY_LATER` close code. + """ + session = root.session_store.get(self._session.sid) + if not session: + raise SessionExpiredException() + with acquire_cursor(session.db) as cr: + env = api.Environment(cr, session.uid, session.context) + if session.uid is not None and not check_session(session, env): + raise SessionExpiredException() + # Mark the notification request as processed. + self.__notif_sock_r.recv(1) + notifications = env['bus.bus']._poll(self._channels, self._last_notif_sent_id) + if not notifications: + return + self._last_notif_sent_id = notifications[-1]['id'] + self._send(notifications) + + +class TimeoutReason(IntEnum): + KEEP_ALIVE = 0 + NO_RESPONSE = 1 + + +class TimeoutManager: + """ + This class handles the Websocket timeouts. If no response to a + PING/CLOSE frame is received after `TIMEOUT` seconds or if the + connection is opened for more than `self._keep_alive_timeout` seconds, + the connection is considered to have timed out. To determine if the + connection has timed out, use the `has_timed_out` method. + """ + TIMEOUT = 15 + # Timeout specifying how many seconds the connection should be kept + # alive. + KEEP_ALIVE_TIMEOUT = int(config['websocket_keep_alive_timeout']) + + def __init__(self): + super().__init__() + self._awaited_opcode = None + # Time in which the connection was opened. + self._opened_at = time.time() + # Custom keep alive timeout for each TimeoutManager to avoid multiple + # connections timing out at the same time. + self._keep_alive_timeout = ( + self.KEEP_ALIVE_TIMEOUT + random.uniform(0, self.KEEP_ALIVE_TIMEOUT / 2) + ) + self.timeout_reason = None + # Start time recorded when we started awaiting an answer to a + # PING/CLOSE frame. + self._waiting_start_time = None + + def acknowledge_frame_receipt(self, frame): + if self._awaited_opcode is frame.opcode: + self._awaited_opcode = None + self._waiting_start_time = None + + def acknowledge_frame_sent(self, frame): + """ + Acknowledge a frame was sent. If this frame is a PING/CLOSE + frame, start waiting for an answer. + """ + if self.has_timed_out(): + return + if frame.opcode is Opcode.PING: + self._awaited_opcode = Opcode.PONG + elif frame.opcode is Opcode.CLOSE: + self._awaited_opcode = Opcode.CLOSE + if self._awaited_opcode is not None: + self._waiting_start_time = time.time() + + def has_timed_out(self): + """ + Determine whether the connection has timed out or not. The + connection times out when the answer to a CLOSE/PING frame + is not received within `TIMEOUT` seconds or if the connection + is opened for more than `self._keep_alive_timeout` seconds. + """ + now = time.time() + if now - self._opened_at >= self._keep_alive_timeout: + self.timeout_reason = TimeoutReason.KEEP_ALIVE + return True + if self._awaited_opcode and now - self._waiting_start_time >= self.TIMEOUT: + self.timeout_reason = TimeoutReason.NO_RESPONSE + return True + return False + + +# ------------------------------------------------------ +# WEBSOCKET SERVING +# ------------------------------------------------------ + + +_wsrequest_stack = LocalStack() +wsrequest = _wsrequest_stack() + +class WebsocketRequest: + def __init__(self, db, httprequest, websocket): + self.db = db + self.httprequest = httprequest + self.session = None + self.ws = websocket + + def __enter__(self): + _wsrequest_stack.push(self) + return self + + def __exit__(self, *args): + _wsrequest_stack.pop() + + def serve_websocket_message(self, message): + try: + jsonrequest = json.loads(message) + event_name = jsonrequest['event_name'] # mandatory + except KeyError as exc: + raise InvalidWebsocketRequest( + f'Key {exc.args[0]!r} is missing from request' + ) from exc + except ValueError as exc: + raise InvalidWebsocketRequest( + f'Invalid JSON data, {exc.args[0]}' + ) from exc + data = jsonrequest.get('data') + self.session = self._get_session() + + try: + self.registry = Registry(self.db) + self.registry.check_signaling() + except ( + AttributeError, psycopg2.OperationalError, psycopg2.ProgrammingError + ) as exc: + raise InvalidDatabaseException() from exc + + with closing(acquire_cursor(self.db)) as cr: + self.env = api.Environment(cr, self.session.uid, self.session.context) + threading.current_thread().uid = self.env.uid + service_model.retrying( + functools.partial(self._serve_ir_websocket, event_name, data), + self.env, + ) + + def _serve_ir_websocket(self, event_name, data): + """ + Delegate most of the processing to the ir.websocket model + which is extensible by applications. Directly call the + appropriate ir.websocket method since only two events are + tolerated: `subscribe` and `update_presence`. + """ + self.env['ir.websocket']._authenticate() + if event_name == 'subscribe': + self.env['ir.websocket']._subscribe(data) + if event_name == 'update_presence': + self.env['ir.websocket']._update_bus_presence(**data) + + def _get_session(self): + session = root.session_store.get(self.ws._session.sid) + if not session: + raise SessionExpiredException() + return session + + def update_env(self, user=None, context=None, su=None): + """ + Update the environment of the current websocket request. + """ + Request.update_env(self, user, context, su) + + def update_context(self, **overrides): + """ + Override the environment context of the current request with the + values of ``overrides``. To replace the entire context, please + use :meth:`~update_env` instead. + """ + self.update_env(context=dict(self.env.context, **overrides)) + + +class WebsocketConnectionHandler: + SUPPORTED_VERSIONS = {'13'} + # Given by the RFC in order to generate Sec-WebSocket-Accept from + # Sec-WebSocket-Key value. + _HANDSHAKE_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + _REQUIRED_HANDSHAKE_HEADERS = { + 'connection', 'host', 'sec-websocket-key', + 'sec-websocket-version', 'upgrade', 'origin', + } + + @classmethod + def websocket_allowed(cls, request): + return not request.registry.in_test_mode() + + @classmethod + def open_connection(cls, request): + """ + Open a websocket connection if the handshake is successfull. + :return: Response indicating the server performed a connection + upgrade. + :raise: UpgradeRequired if there is no intersection between the + versions the client supports and those we support. + :raise: BadRequest if the handshake data is incorrect. + """ + if not cls.websocket_allowed(request): + raise ServiceUnavailable("Websocket is disabled in test mode") + cls._handle_public_configuration(request) + try: + response = cls._get_handshake_response(request.httprequest.headers) + socket = request.httprequest._HTTPRequest__environ['socket'] + session, db, httprequest = request.session, request.db, request.httprequest + response.call_on_close(lambda: cls._serve_forever( + Websocket(socket, session), + db, + httprequest, + )) + # Force save the session. Session must be persisted to handle + # WebSocket authentication. + request.session.is_dirty = True + return response + except KeyError as exc: + raise RuntimeError( + f"Couldn't bind the websocket. Is the connection opened on the evented port ({config['gevent_port']})?" + ) from exc + except HTTPException as exc: + # The HTTP stack does not log exceptions derivated from the + # HTTPException class since they are valid responses. + _logger.error(exc) + raise + + + + @classmethod + def _get_handshake_response(cls, headers): + """ + :return: Response indicating the server performed a connection + upgrade. + :raise: BadRequest + :raise: UpgradeRequired + """ + cls._assert_handshake_validity(headers) + # sha-1 is used as it is required by + # https://datatracker.ietf.org/doc/html/rfc6455#page-7 + accept_header = hashlib.sha1( + (headers['sec-websocket-key'] + cls._HANDSHAKE_GUID).encode()).digest() + accept_header = base64.b64encode(accept_header) + return Response(status=101, headers={ + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Accept': accept_header.decode(), + }) + + @classmethod + def _handle_public_configuration(cls, request): + if not os.getenv('ODOO_BUS_PUBLIC_SAMESITE_WS'): + return + headers = request.httprequest.headers + origin_url = urlparse(headers.get('origin')) + if origin_url.netloc != headers.get('host') or origin_url.scheme != request.httprequest.scheme: + request.session = root.session_store.new() + request.session.update(get_default_session(), db=request.session.db) + request.session.is_explicit = True + + @classmethod + def _assert_handshake_validity(cls, headers): + """ + :raise: UpgradeRequired if there is no intersection between + the version the client supports and those we support. + :raise: BadRequest in case of invalid handshake. + """ + missing_or_empty_headers = { + header for header in cls._REQUIRED_HANDSHAKE_HEADERS + if header not in headers + } + if missing_or_empty_headers: + raise BadRequest( + f"""Empty or missing header(s): {', '.join(missing_or_empty_headers)}""" + ) + + if headers['upgrade'].lower() != 'websocket': + raise BadRequest('Invalid upgrade header') + if 'upgrade' not in headers['connection'].lower(): + raise BadRequest('Invalid connection header') + if headers['sec-websocket-version'] not in cls.SUPPORTED_VERSIONS: + raise UpgradeRequired() + + key = headers['sec-websocket-key'] + try: + decoded_key = base64.b64decode(key, validate=True) + except ValueError: + raise BadRequest("Sec-WebSocket-Key should be b64 encoded") + if len(decoded_key) != 16: + raise BadRequest( + "Sec-WebSocket-Key should be of length 16 once decoded" + ) + + @classmethod + def _serve_forever(cls, websocket, db, httprequest): + """ + Process incoming messages and dispatch them to the application. + """ + current_thread = threading.current_thread() + current_thread.type = 'websocket' + for message in websocket.get_messages(): + with WebsocketRequest(db, httprequest, websocket) as req: + try: + req.serve_websocket_message(message) + except SessionExpiredException: + websocket.disconnect(CloseCode.SESSION_EXPIRED) + except PoolError: + websocket.disconnect(CloseCode.TRY_LATER) + except Exception: + _logger.exception("Exception occurred during websocket request handling") + + +def _kick_all(): + """ Disconnect all the websocket instances. """ + for websocket in _websocket_instances: + if websocket.state is ConnectionState.OPEN: + websocket.disconnect(CloseCode.GOING_AWAY) + + +CommonServer.on_stop(_kick_all)