first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,40 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Мобилен"
#, fuzzy
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Код"
#, fuzzy
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Потребител"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mòbil"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Codi"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Usuari"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Número de telèfon que suporta rebre SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s codi %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Codi SMS per %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Codi d'SMS per a l'inici de sessió de l'usuari"

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobiltelefon"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Code"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Benutzer"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "Benutzer ID"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Eine Telefonnummer, die den Empfang von SMS unterstützt."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s Code %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "SMS Code für %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Benutzeranmeldung SMS-Code"

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Móvil"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Código"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Usuario"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID usuario"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Número de teléfono que puede recibir SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s código %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Código SMS para %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Código Login Usuario SMS"

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobiil"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Kood"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Kasutaja"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "Kasutaja ID"
#, fuzzy
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Telefoninumber mis toetab SMSi vastuvõtmist"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(nimi) kood %(kood)id"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "موبایل"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "کد"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "کاربر"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "شناسه کاربر"
#, fuzzy
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "شماره تلفن همراه که پیام کوتاه را پشتیبانی کند"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "\"%(name)s\" کد \"%(کد)s\""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobile"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Code"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Utilisateur"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID d'utilisateur"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Numéro de téléphone qui support la réception de SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s code %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Code SMS pour %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Code SMS de connexion utilisateur"

View File

@@ -0,0 +1,40 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobiltelefon"
#, fuzzy
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Partner kód"
#, fuzzy
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Felhasználó"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobile"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Kode"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Pengguna"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID Pengguna"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Nomor telepon yang mendukung penerimaan SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s kode %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Kode SMS untuk %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Cellulare"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Codice"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Utente"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID utente"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Numero telefonico abilitato alla ricezione di SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s codice %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Codice SMS per %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "ໂທລະສັບມືຖື"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "ລະຫັດ"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "ຜູ້ໃຊ້"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ລະຫັດຜູ້ໃຊ້"
#, fuzzy
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "ເລກໂທລະສັບ ທີ່ຮອງຮັບ ການຮັບຂໍ້ຄວາມສັ້ນ"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s ລະຫັດ %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobiel"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Code"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Gebruiker"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "Gebruiker ID"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Telefoonnummer dat het ontvangen van smssen ondersteunt."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s code %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "SMS-code voor%(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Gebruiker login sms-code"

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Komórka"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Kod"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Użytkownik"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID użytkownika"
#, fuzzy
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Numer telefonu, który umożliwia otrzymywanie SMS-ów"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s kod %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Celular"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Código"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Usuário"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID do Usuário"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Número de telefone que pode receber SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s código %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobil"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Cod"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Utilizator"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID-ul de utilizator"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Număr de telefon care acceptă primirea de SMS-uri."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s cod %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "Cod SMS pentru %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr "Cod SMS Logare Utilizator"

View File

@@ -0,0 +1,40 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "сот.телефон"
#, fuzzy
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Код языка"
#, fuzzy
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Пользователь"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr "Mobilni"
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "Šifra"
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "Uporabnik"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr "ID uporabnika"
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr "Telefonska številka, ki omogoča prejem SMS."
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr "%(name)s šifra %(code)s"
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr "SMS koda za %(login)s"
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr ""
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr ""
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,39 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:res.user,mobile:"
msgid "Mobile"
msgstr ""
#, fuzzy
msgctxt "field:res.user.login.sms_code,code:"
msgid "Code"
msgstr "语言编码"
#, fuzzy
msgctxt "field:res.user.login.sms_code,user:"
msgid "User"
msgstr "用户"
msgctxt "field:res.user.login.sms_code,user_id:"
msgid "User ID"
msgstr ""
msgctxt "help:res.user,mobile:"
msgid "Phone number that supports receiving SMS."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sms_text"
msgid "%(name)s code %(code)s"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_user_sms_code"
msgid "SMS Code for %(login)s"
msgstr ""
msgctxt "model:res.user.login.sms_code,string:"
msgid "User Login Sms Code"
msgstr ""

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_sms_text">
<field name="text">%(name)s code %(code)s</field>
</record>
<record model="ir.message" id="msg_user_sms_code">
<field name="text">SMS Code for %(login)s</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,113 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
import logging
import random
import trytond.config as config
from trytond.exceptions import LoginException
from trytond.i18n import gettext
from trytond.model import Index, ModelSQL, fields
from trytond.pool import Pool, PoolMeta
from trytond.tools import resolve
logger = logging.getLogger(__name__)
def send_sms(text, to):
if func := config.get('authentication_sms', 'function', default=None):
func = resolve(func)
from_ = config.get('authentication_sms', 'from', default=None)
return func(text, to, from_)
logger.error('Could not send SMS to %s: "%s"', to, text)
class User(metaclass=PoolMeta):
__name__ = 'res.user'
mobile = fields.Char('Mobile',
help='Phone number that supports receiving SMS.')
@classmethod
def __setup__(cls):
super().__setup__()
cls._preferences_fields.append('mobile')
@classmethod
def _login_sms(cls, login, parameters):
pool = Pool()
SMSCode = pool.get('res.user.login.sms_code')
user_id = cls._get_login(login)[0]
if user_id:
SMSCode.send(user_id)
if 'sms_code' in parameters:
code = parameters['sms_code']
if not code:
return
if SMSCode.check(user_id, code):
return user_id
msg = SMSCode.fields_get(['code'])['code']['string']
msg = gettext('authentication_sms.msg_user_sms_code', login=login)
raise LoginException('sms_code', msg, type='char')
class UserLoginSMSCode(ModelSQL):
"""This class is separated from the res.user one in order to prevent
locking the res.user table when in a long running process.
"""
__name__ = 'res.user.login.sms_code'
user_id = fields.Integer("User ID")
user = fields.Function(fields.Many2One('res.user', 'User'), 'get_user')
code = fields.Char('Code')
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_indexes.add(Index(
t, (t.user_id, Index.Equality(cardinality='high'))))
@classmethod
def default_code(cls):
length = config.getint('authentication_sms', 'length', default=6)
srandom = random.SystemRandom()
return ''.join(str(srandom.randint(0, 9)) for _ in range(length))
def get_user(self, name):
return self.user_id
@classmethod
def get(cls, user, _now=None):
if _now is None:
_now = datetime.datetime.now()
timeout = datetime.timedelta(
seconds=config.getint('authentication_sms', 'ttl', default=5 * 60))
records = cls.search([
('user_id', '=', user),
])
for record in records:
if abs(record.create_date - _now) < timeout:
yield record
else:
cls.delete([record])
@classmethod
def send(cls, user, mobile=None):
if not list(cls.get(user)) or mobile:
record = cls(user_id=user)
record.save()
name = config.get('authentication_sms', 'name', default='Tryton')
text = gettext('authentication_sms.msg_sms_text',
name=name, code=record.code)
if mobile:
send_sms(text, mobile)
elif record.user.mobile:
send_sms(text, record.user.mobile)
@classmethod
def check(cls, user, code):
for record in cls.get(user):
if record.code == code:
cls.delete([record])
return True
return False

View File

@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view" id="user_view_form">
<field name="model">res.user</field>
<field name="inherit" ref="res.user_view_form"/>
<field name="name">user_form</field>
</record>
<record model="ir.ui.view" id="user_view_form_preferences">
<field name="model">res.user</field>
<field name="inherit" ref="res.user_view_form_preferences"/>
<field name="name">user_form_preferences</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,5 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from .test_module import send_sms
__all__ = ['send_sms']

View File

@@ -0,0 +1,117 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
import trytond.config as config
from trytond.exceptions import LoginException
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
def send_sms(text, to, from_):
sms_queue.append({
'text': text,
'to': to,
'from': from_,
})
sms_queue = []
class AuthenticationSMSTestCase(ModuleTestCase):
'Test Authentication SMS module'
module = 'authentication_sms'
def setUp(self):
super().setUp()
methods = config.get('session', 'authentications', default='')
config.set('session', 'authentications', 'sms')
self.addCleanup(config.set, 'session', 'authentications', methods)
config.add_section('authentication_sms')
config.set(
'authentication_sms', 'function',
'.'.join([send_sms.__module__, send_sms.__qualname__]))
self.addCleanup(config.remove_section, 'authentication_sms')
del sms_queue[:]
@with_transaction()
def test_sms_code_default_code(self):
pool = Pool()
SMSCode = pool.get('res.user.login.sms_code')
code = SMSCode.default_code()
self.assertEqual(len(code), 6)
@with_transaction()
def test_sms_code_get(self):
pool = Pool()
SMSCode = pool.get('res.user.login.sms_code')
record, = SMSCode.create([{'user_id': 1}])
records = list(SMSCode.get(1))
self.assertEqual(records, [record])
future = datetime.datetime.now() + datetime.timedelta(10 * 60)
records = list(SMSCode.get(1, _now=future))
self.assertFalse(records)
self.assertFalse(SMSCode.search([]))
@with_transaction()
def test_sms_code_send(self):
pool = Pool()
User = pool.get('res.user')
SMSCode = pool.get('res.user.login.sms_code')
user = User(name='sms', login='sms', mobile='+123456789')
user.save()
SMSCode.send(user.id)
record, = SMSCode.search([])
self.assertEqual(len(sms_queue), 1)
sms, = sms_queue
self.assertEqual(record.user_id, user.id)
self.assertIn(record.code, sms['text'])
self.assertEqual(user.mobile, sms['to'])
# Don't send a second SMS as long as the first is valid
SMSCode.send(user.id)
self.assertEqual(len(sms_queue), 1)
@with_transaction()
def test_sms_code_check(self):
pool = Pool()
SMSCode = pool.get('res.user.login.sms_code')
record, = SMSCode.create([{'user_id': 1}])
sms_code = record.code
self.assertFalse(SMSCode.check(1, 'foo'))
self.assertTrue(SMSCode.check(1, sms_code))
# Second check should fail
self.assertFalse(SMSCode.check(1, sms_code))
@with_transaction()
def test_user_get_login(self):
pool = Pool()
User = pool.get('res.user')
SMSCode = pool.get('res.user.login.sms_code')
user = User(name='sms', login='sms', mobile='+123456789')
user.save()
with self.assertRaises(LoginException) as cm:
User.get_login('sms', {})
self.assertEqual(cm.exception.name, 'sms_code')
self.assertEqual(cm.exception.type, 'char')
record, = SMSCode.search([])
sms_code = record.code
user_id = User.get_login('sms', {
'sms_code': sms_code,
})
self.assertEqual(user_id, user.id)
del ModuleTestCase

View File

@@ -0,0 +1,13 @@
[tryton]
version=7.8.0
depends:
ir
res
xml:
res.xml
message.xml
[register]
model:
res.User
res.UserLoginSMSCode

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<data>
<xpath expr="//field[@name='email']" position="after">
<label name="mobile"/>
<field name="mobile"/>
<newline/>
</xpath>
</data>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<data>
<xpath expr="//field[@name='password']" position="after">
<label name="mobile"/>
<field name="mobile"/>
<newline/>
</xpath>
</data>