mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'dev' into reformat-fr-translation
This commit is contained in:
commit
13fd447b6f
12 changed files with 783 additions and 243 deletions
|
@ -2,7 +2,7 @@
|
||||||
# TRANSLATION
|
# TRANSLATION
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
remove-stale-translated-strings:
|
autofix-translated-strings:
|
||||||
stage: translation
|
stage: translation
|
||||||
image: "before-install"
|
image: "before-install"
|
||||||
needs: []
|
needs: []
|
||||||
|
@ -14,8 +14,9 @@ remove-stale-translated-strings:
|
||||||
script:
|
script:
|
||||||
- cd tests # Maybe move this script location to another folder?
|
- cd tests # Maybe move this script location to another folder?
|
||||||
# create a local branch that will overwrite distant one
|
# create a local branch that will overwrite distant one
|
||||||
- git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track
|
- git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track
|
||||||
- python3 remove_stale_translated_strings.py
|
- python3 remove_stale_translated_strings.py
|
||||||
|
- python3 autofix_locale_format.py
|
||||||
- python3 reformat_locales.py
|
- python3 reformat_locales.py
|
||||||
- '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
|
- '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
|
||||||
- git commit -am "[CI] Reformat / remove stale translated strings" || true
|
- git commit -am "[CI] Reformat / remove stale translated strings" || true
|
||||||
|
|
|
@ -59,7 +59,7 @@ user:
|
||||||
api: GET /users
|
api: GET /users
|
||||||
arguments:
|
arguments:
|
||||||
--fields:
|
--fields:
|
||||||
help: fields to fetch
|
help: fields to fetch (username, fullname, mail, mail-alias, mail-forward, mailbox-quota, groups, shell, home-path)
|
||||||
nargs: "+"
|
nargs: "+"
|
||||||
|
|
||||||
### user_create()
|
### user_create()
|
||||||
|
@ -199,6 +199,28 @@ user:
|
||||||
username:
|
username:
|
||||||
help: Username or email to get information
|
help: Username or email to get information
|
||||||
|
|
||||||
|
### user_export()
|
||||||
|
export:
|
||||||
|
action_help: Export users into CSV
|
||||||
|
api: GET /users/export
|
||||||
|
|
||||||
|
### user_import()
|
||||||
|
import:
|
||||||
|
action_help: Import several users from CSV
|
||||||
|
api: POST /users/import
|
||||||
|
arguments:
|
||||||
|
csvfile:
|
||||||
|
help: "CSV file with columns username, firstname, lastname, password, mail, mailbox-quota, mail-alias, mail-forward, groups (separated by coma)"
|
||||||
|
type: open
|
||||||
|
-u:
|
||||||
|
full: --update
|
||||||
|
help: Update all existing users contained in the CSV file (by default existing users are ignored)
|
||||||
|
action: store_true
|
||||||
|
-d:
|
||||||
|
full: --delete
|
||||||
|
help: Delete all existing users that are not contained in the CSV file (by default existing users are kept)
|
||||||
|
action: store_true
|
||||||
|
|
||||||
subcategories:
|
subcategories:
|
||||||
group:
|
group:
|
||||||
subcategory_help: Manage user groups
|
subcategory_help: Manage user groups
|
||||||
|
|
|
@ -33,6 +33,7 @@ olcAuthzPolicy: none
|
||||||
olcConcurrency: 0
|
olcConcurrency: 0
|
||||||
olcConnMaxPending: 100
|
olcConnMaxPending: 100
|
||||||
olcConnMaxPendingAuth: 1000
|
olcConnMaxPendingAuth: 1000
|
||||||
|
olcSizeLimit: 50000
|
||||||
olcIdleTimeout: 0
|
olcIdleTimeout: 0
|
||||||
olcIndexSubstrIfMaxLen: 4
|
olcIndexSubstrIfMaxLen: 4
|
||||||
olcIndexSubstrIfMinLen: 2
|
olcIndexSubstrIfMinLen: 2
|
||||||
|
@ -188,7 +189,7 @@ olcDbIndex: memberUid eq
|
||||||
olcDbIndex: uniqueMember eq
|
olcDbIndex: uniqueMember eq
|
||||||
olcDbIndex: virtualdomain eq
|
olcDbIndex: virtualdomain eq
|
||||||
olcDbIndex: permission eq
|
olcDbIndex: permission eq
|
||||||
olcDbMaxSize: 10485760
|
olcDbMaxSize: 104857600
|
||||||
structuralObjectClass: olcMdbConfig
|
structuralObjectClass: olcMdbConfig
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -416,6 +416,7 @@
|
||||||
"log_regen_conf": "Regenerate system configurations '{}'",
|
"log_regen_conf": "Regenerate system configurations '{}'",
|
||||||
"log_user_create": "Add '{}' user",
|
"log_user_create": "Add '{}' user",
|
||||||
"log_user_delete": "Delete '{}' user",
|
"log_user_delete": "Delete '{}' user",
|
||||||
|
"log_user_import": "Import users",
|
||||||
"log_user_group_create": "Create '{}' group",
|
"log_user_group_create": "Create '{}' group",
|
||||||
"log_user_group_delete": "Delete '{}' group",
|
"log_user_group_delete": "Delete '{}' group",
|
||||||
"log_user_group_update": "Update '{}' group",
|
"log_user_group_update": "Update '{}' group",
|
||||||
|
@ -641,10 +642,17 @@
|
||||||
"user_creation_failed": "Could not create user {user}: {error}",
|
"user_creation_failed": "Could not create user {user}: {error}",
|
||||||
"user_deleted": "User deleted",
|
"user_deleted": "User deleted",
|
||||||
"user_deletion_failed": "Could not delete user {user}: {error}",
|
"user_deletion_failed": "Could not delete user {user}: {error}",
|
||||||
"user_home_creation_failed": "Could not create 'home' folder for user",
|
"user_home_creation_failed": "Could not create home folder '{home}' for user",
|
||||||
"user_unknown": "Unknown user: {user}",
|
"user_unknown": "Unknown user: {user}",
|
||||||
"user_update_failed": "Could not update user {user}: {error}",
|
"user_update_failed": "Could not update user {user}: {error}",
|
||||||
"user_updated": "User info changed",
|
"user_updated": "User info changed",
|
||||||
|
"user_import_bad_line": "Incorrect line {line}: {details}",
|
||||||
|
"user_import_bad_file": "Your CSV file is not correctly formatted it will be ignored to avoid potential data loss",
|
||||||
|
"user_import_missing_columns": "The following columns are missing: {columns}",
|
||||||
|
"user_import_partial_failed": "The users import operation partially failed",
|
||||||
|
"user_import_failed": "The users import operation completely failed",
|
||||||
|
"user_import_nothing_to_do": "No user needs to be imported",
|
||||||
|
"user_import_success": "Users successfully imported",
|
||||||
"yunohost_already_installed": "YunoHost is already installed",
|
"yunohost_already_installed": "YunoHost is already installed",
|
||||||
"yunohost_configured": "YunoHost is now configured",
|
"yunohost_configured": "YunoHost is now configured",
|
||||||
"yunohost_installing": "Installing YunoHost...",
|
"yunohost_installing": "Installing YunoHost...",
|
||||||
|
|
|
@ -602,7 +602,7 @@
|
||||||
"restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد",
|
"restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد",
|
||||||
"restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد",
|
"restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد",
|
||||||
"restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)",
|
"restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)",
|
||||||
"restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space} B ، فضای مورد نیاز: {space_space} B ، حاشیه امنیتی: {margin} B)",
|
"restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)",
|
||||||
"restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد",
|
"restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد",
|
||||||
"restore_failed": "سیستم بازیابی نشد",
|
"restore_failed": "سیستم بازیابی نشد",
|
||||||
"restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…",
|
"restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…",
|
||||||
|
|
|
@ -137,15 +137,15 @@
|
||||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé",
|
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé",
|
||||||
"upnp_disabled": "L'UPnP est désactivé",
|
"upnp_disabled": "L'UPnP est désactivé",
|
||||||
"upnp_enabled": "L'UPnP est activé",
|
"upnp_enabled": "L'UPnP est activé",
|
||||||
"upnp_port_open_failed": "Impossible d'ouvrir les ports UPnP",
|
"upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP",
|
||||||
"user_created": "L'utilisateur a été créé",
|
"user_created": "L’utilisateur a été créé",
|
||||||
"user_creation_failed": "Impossible de créer l'utilisateur {user} : {error}",
|
"user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}",
|
||||||
"user_deleted": "L'utilisateur a été supprimé",
|
"user_deleted": "L’utilisateur a été supprimé",
|
||||||
"user_deletion_failed": "Impossible de supprimer l'utilisateur {user} : {error}",
|
"user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}",
|
||||||
"user_home_creation_failed": "Impossible de créer le dossier personnel de l'utilisateur",
|
"user_home_creation_failed": "Impossible de créer le dossier personnel '{home}' de l’utilisateur",
|
||||||
"user_unknown": "L'utilisateur {user} est inconnu",
|
"user_unknown": "L’utilisateur {user} est inconnu",
|
||||||
"user_update_failed": "Impossible de mettre à jour l'utilisateur {user} : {error}",
|
"user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}",
|
||||||
"user_updated": "L'utilisateur a été modifié",
|
"user_updated": "L’utilisateur a été modifié",
|
||||||
"yunohost_already_installed": "YunoHost est déjà installé",
|
"yunohost_already_installed": "YunoHost est déjà installé",
|
||||||
"yunohost_configured": "YunoHost est maintenant configuré",
|
"yunohost_configured": "YunoHost est maintenant configuré",
|
||||||
"yunohost_installing": "L'installation de YunoHost est en cours...",
|
"yunohost_installing": "L'installation de YunoHost est en cours...",
|
||||||
|
@ -169,10 +169,10 @@
|
||||||
"certmanager_unable_to_parse_self_CA_name": "Impossible d'analyser le nom de l'autorité du certificat auto-signé (fichier : {file})",
|
"certmanager_unable_to_parse_self_CA_name": "Impossible d'analyser le nom de l'autorité du certificat auto-signé (fichier : {file})",
|
||||||
"mailbox_used_space_dovecot_down": "Le service Dovecot doit être démarré si vous souhaitez voir l'espace disque occupé par la messagerie",
|
"mailbox_used_space_dovecot_down": "Le service Dovecot doit être démarré si vous souhaitez voir l'espace disque occupé par la messagerie",
|
||||||
"domains_available": "Domaines disponibles :",
|
"domains_available": "Domaines disponibles :",
|
||||||
"backup_archive_broken_link": "Impossible d'accéder à l'archive de sauvegarde (lien invalide vers {path})",
|
"backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path})",
|
||||||
"certmanager_acme_not_configured_for_domain": "Le challenge ACME n'a pas pu être validé pour le domaine {domain} pour le moment car le code de la configuration NGINX est manquant... Merci de vérifier que votre configuration NGINX est à jour avec la commande : `yunohost tools regen-conf nginx --dry-run --with-diff`.",
|
"certmanager_acme_not_configured_for_domain": "Le challenge ACME n'a pas pu être validé pour le domaine {domain} pour le moment car le code de la configuration NGINX est manquant... Merci de vérifier que votre configuration NGINX est à jour avec la commande : `yunohost tools regen-conf nginx --dry-run --with-diff`.",
|
||||||
"domain_hostname_failed": "Échec de l'utilisation d'un nouveau nom d'hôte. Cela pourrait causer des soucis plus tard (cela n'en causera peut-être pas).",
|
"domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d'hôte. Cela pourrait causer des soucis plus tard (cela n'en causera peut-être pas).",
|
||||||
"app_already_installed_cant_change_url": "Cette application est déjà installée. L'URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.",
|
"app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.",
|
||||||
"app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}",
|
"app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}",
|
||||||
"app_change_url_identical_domains": "L'ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain}{path}'), rien à faire.",
|
"app_change_url_identical_domains": "L'ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain}{path}'), rien à faire.",
|
||||||
"app_change_url_no_script": "L'application '{app_name}' ne prend pas encore en charge le changement d'URL. Vous devriez peut-être la mettre à jour.",
|
"app_change_url_no_script": "L'application '{app_name}' ne prend pas encore en charge le changement d'URL. Vous devriez peut-être la mettre à jour.",
|
||||||
|
@ -649,5 +649,13 @@
|
||||||
"diagnosis_apps_not_in_app_catalog": "Cette application est absente ou ne figure plus dans le catalogue d'applications de YunoHost. Vous devriez envisager de la désinstaller car elle ne recevra pas de mise à jour et pourrait compromettre l'intégrité et la sécurité de votre système.",
|
"diagnosis_apps_not_in_app_catalog": "Cette application est absente ou ne figure plus dans le catalogue d'applications de YunoHost. Vous devriez envisager de la désinstaller car elle ne recevra pas de mise à jour et pourrait compromettre l'intégrité et la sécurité de votre système.",
|
||||||
"diagnosis_apps_issue": "Un problème a été détecté pour l'application {app}",
|
"diagnosis_apps_issue": "Un problème a été détecté pour l'application {app}",
|
||||||
"diagnosis_apps_allgood": "Toutes les applications installées respectent les pratiques de packaging de base",
|
"diagnosis_apps_allgood": "Toutes les applications installées respectent les pratiques de packaging de base",
|
||||||
"diagnosis_description_apps": "Applications"
|
"diagnosis_description_apps": "Applications",
|
||||||
|
"user_import_success": "Utilisateurs importés avec succès",
|
||||||
|
"user_import_nothing_to_do": "Aucun utilisateur n'a besoin d'être importé",
|
||||||
|
"user_import_failed": "L'opération d'importation des utilisateurs a totalement échoué",
|
||||||
|
"user_import_partial_failed": "L'opération d'importation des utilisateurs a partiellement échoué",
|
||||||
|
"user_import_missing_columns": "Les colonnes suivantes sont manquantes : {columns}",
|
||||||
|
"user_import_bad_file": "Votre fichier CSV n'est pas correctement formaté, il sera ignoré afin d'éviter une potentielle perte de données",
|
||||||
|
"user_import_bad_line": "Ligne incorrecte {line} : {details}",
|
||||||
|
"log_user_import": "Importer des utilisateurs"
|
||||||
}
|
}
|
||||||
|
|
|
@ -546,7 +546,7 @@
|
||||||
"regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'",
|
"regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'",
|
||||||
"service_enable_failed": "Non se puido facer que o servizo '{service}' se inicie automáticamente no inicio.\n\nRexistros recentes do servizo: {logs}",
|
"service_enable_failed": "Non se puido facer que o servizo '{service}' se inicie automáticamente no inicio.\n\nRexistros recentes do servizo: {logs}",
|
||||||
"service_disabled": "O servizo '{service}' xa non vai volver a ser iniciado ao inicio do sistema.",
|
"service_disabled": "O servizo '{service}' xa non vai volver a ser iniciado ao inicio do sistema.",
|
||||||
"service_disable_failed": "Non se puido iniciar o servizo '{servizo}' ao inicio.\n\nRexistro recente do servizo: {logs}",
|
"service_disable_failed": "Non se puido iniciar o servizo '{service}' ao inicio.\n\nRexistro recente do servizo: {logs}",
|
||||||
"service_description_yunohost-firewall": "Xestiona, abre e pecha a conexións dos portos aos servizos",
|
"service_description_yunohost-firewall": "Xestiona, abre e pecha a conexións dos portos aos servizos",
|
||||||
"service_description_yunohost-api": "Xestiona as interaccións entre a interface web de YunoHost e o sistema",
|
"service_description_yunohost-api": "Xestiona as interaccións entre a interface web de YunoHost e o sistema",
|
||||||
"service_description_ssh": "Permíteche conectar de xeito remoto co teu servidor a través dun terminal (protocolo SSH)",
|
"service_description_ssh": "Permíteche conectar de xeito remoto co teu servidor a través dun terminal (protocolo SSH)",
|
||||||
|
@ -563,7 +563,7 @@
|
||||||
"service_description_dnsmasq": "Xestiona a resolución de nomes de dominio (DNS)",
|
"service_description_dnsmasq": "Xestiona a resolución de nomes de dominio (DNS)",
|
||||||
"service_description_yunomdns": "Permíteche chegar ao teu servidor utilizando 'yunohost.local' na túa rede local",
|
"service_description_yunomdns": "Permíteche chegar ao teu servidor utilizando 'yunohost.local' na túa rede local",
|
||||||
"service_cmd_exec_failed": "Non se puido executar o comando '{command}'",
|
"service_cmd_exec_failed": "Non se puido executar o comando '{command}'",
|
||||||
"service_already_stopped": "O servizo '{sevice}' xa está detido",
|
"service_already_stopped": "O servizo '{service}' xa está detido",
|
||||||
"service_already_started": "O servizo '{service}' xa se está a executar",
|
"service_already_started": "O servizo '{service}' xa se está a executar",
|
||||||
"service_added": "Foi engadido o servizo '{service}'",
|
"service_added": "Foi engadido o servizo '{service}'",
|
||||||
"service_add_failed": "Non se puido engadir o servizo '{service}'",
|
"service_add_failed": "Non se puido engadir o servizo '{service}'",
|
||||||
|
|
306
locales/uk.json
306
locales/uk.json
|
@ -55,20 +55,20 @@
|
||||||
"service_description_postfix": "Використовується для відправки та отримання електронної пошти",
|
"service_description_postfix": "Використовується для відправки та отримання електронної пошти",
|
||||||
"service_description_php7.3-fpm": "Запускає програми, написані на мові програмування PHP, за допомогою NGINX",
|
"service_description_php7.3-fpm": "Запускає програми, написані на мові програмування PHP, за допомогою NGINX",
|
||||||
"service_description_nginx": "Обслуговує або надає доступ до всіх веб-сайтів, розміщених на вашому сервері",
|
"service_description_nginx": "Обслуговує або надає доступ до всіх веб-сайтів, розміщених на вашому сервері",
|
||||||
"service_description_mysql": "Зберігає дані додатків (база даних SQL)",
|
"service_description_mysql": "Зберігає дані застосунків (база даних SQL)",
|
||||||
"service_description_metronome": "Служба захисту миттєвого обміну повідомленнями XMPP",
|
"service_description_metronome": "Служба захисту миттєвого обміну повідомленнями XMPP",
|
||||||
"service_description_fail2ban": "Захист від перебору та інших видів атак з Інтернету",
|
"service_description_fail2ban": "Захист від перебору та інших видів атак з Інтернету",
|
||||||
"service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)",
|
"service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)",
|
||||||
"service_description_dnsmasq": "Обробляє дозвіл доменних імен (DNS)",
|
"service_description_dnsmasq": "Обробляє дозвіл доменних імен (DNS)",
|
||||||
"service_description_yunomdns": "Дозволяє вам отримати доступ до вашого сервера, використовуючи 'yunohost.local' у вашій локальній мережі",
|
"service_description_yunomdns": "Дозволяє вам отримати доступ до вашого сервера, використовуючи 'yunohost.local' у вашій локальній мережі",
|
||||||
"service_cmd_exec_failed": "Не вдалося виконати команду '{команда}'",
|
"service_cmd_exec_failed": "Не вдалося виконати команду '{command}'",
|
||||||
"service_already_stopped": "Служба '{service}' вже зупинена",
|
"service_already_stopped": "Служба '{service}' вже зупинена",
|
||||||
"service_already_started": "Служба '{service}' вже запущена",
|
"service_already_started": "Служба '{service}' вже запущена",
|
||||||
"service_added": "Служба '{service}' була додана",
|
"service_added": "Служба '{service}' була додана",
|
||||||
"service_add_failed": "Не вдалося додати службу '{service}'",
|
"service_add_failed": "Не вдалося додати службу '{service}'",
|
||||||
"server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{Відповіді}]",
|
"server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{answers}]",
|
||||||
"server_reboot": "сервер перезавантажиться",
|
"server_reboot": "сервер перезавантажиться",
|
||||||
"server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{Відповіді}].",
|
"server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{answers}].",
|
||||||
"server_shutdown": "сервер вимкнеться",
|
"server_shutdown": "сервер вимкнеться",
|
||||||
"root_password_replaced_by_admin_password": "Ваш кореневої пароль був замінений на пароль адміністратора.",
|
"root_password_replaced_by_admin_password": "Ваш кореневої пароль був замінений на пароль адміністратора.",
|
||||||
"root_password_desynchronized": "Пароль адміністратора був змінений, але YunoHost не зміг поширити це на пароль root!",
|
"root_password_desynchronized": "Пароль адміністратора був змінений, але YunoHost не зміг поширити це на пароль root!",
|
||||||
|
@ -82,21 +82,21 @@
|
||||||
"restore_hook_unavailable": "Сценарій відновлення для '{part}' недоступним у вашій системі і в архіві його теж немає",
|
"restore_hook_unavailable": "Сценарій відновлення для '{part}' недоступним у вашій системі і в архіві його теж немає",
|
||||||
"restore_failed": "Не вдалося відновити систему",
|
"restore_failed": "Не вдалося відновити систему",
|
||||||
"restore_extracting": "Витяг необхідних файлів з архіву…",
|
"restore_extracting": "Витяг необхідних файлів з архіву…",
|
||||||
"restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{Відповіді}].",
|
"restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{answers}].",
|
||||||
"restore_complete": "відновлення завершено",
|
"restore_complete": "відновлення завершено",
|
||||||
"restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення",
|
"restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення",
|
||||||
"restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.",
|
"restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.",
|
||||||
"restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}",
|
"restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}",
|
||||||
"restore_already_installed_app": "Додаток з ідентифікатором \"{app} 'вже встановлено",
|
"restore_already_installed_app": "Застосунок з ідентифікатором \"{app} 'вже встановлено",
|
||||||
"regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.",
|
"regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.",
|
||||||
"regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.",
|
"regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.",
|
||||||
"regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.",
|
"regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.",
|
||||||
"regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{категорія}'...",
|
"regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{category}'...",
|
||||||
"regenconf_failed": "Не вдалося відновити конфігурацію для категорії (категорій): {categories}",
|
"regenconf_failed": "Не вдалося відновити конфігурацію для категорії (категорій): {categories}",
|
||||||
"regenconf_dry_pending_applying": "Перевірка очікує конфігурації, яка була б застосована для категорії '{категорія}'…",
|
"regenconf_dry_pending_applying": "Перевірка очікує конфігурації, яка була б застосована для категорії '{category}'…",
|
||||||
"regenconf_would_be_updated": "Конфігурація була б оновлена для категорії '{категорія}'",
|
"regenconf_would_be_updated": "Конфігурація була б оновлена для категорії '{category}'",
|
||||||
"regenconf_updated": "Конфігурація оновлена для категорії '{category}'",
|
"regenconf_updated": "Конфігурація оновлена для категорії '{category}'",
|
||||||
"regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{категорія}'",
|
"regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{category}'",
|
||||||
"regenconf_now_managed_by_yunohost": "Конфігураційний файл '{conf}' тепер управляється YunoHost (категорія {category}).",
|
"regenconf_now_managed_by_yunohost": "Конфігураційний файл '{conf}' тепер управляється YunoHost (категорія {category}).",
|
||||||
"regenconf_file_updated": "Конфігураційний файл '{conf}' оновлений",
|
"regenconf_file_updated": "Конфігураційний файл '{conf}' оновлений",
|
||||||
"regenconf_file_removed": "Конфігураційний файл '{conf}' видалений",
|
"regenconf_file_removed": "Конфігураційний файл '{conf}' видалений",
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
"permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {error}",
|
"permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {error}",
|
||||||
"permission_deleted": "Дозвіл '{permission}' видалено",
|
"permission_deleted": "Дозвіл '{permission}' видалено",
|
||||||
"permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.",
|
"permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.",
|
||||||
"permission_currently_allowed_for_all_users": "В даний час цей дозвіл надається всім користувачам на додаток до інших груп. Ймовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким воно зараз надано.",
|
"permission_currently_allowed_for_all_users": "Наразі цей дозвіл надається всім користувачам на додачу до інших груп. Імовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким його зараз надано.",
|
||||||
"permission_creation_failed": "Не вдалося створити дозвіл '{permission}': {error}",
|
"permission_creation_failed": "Не вдалося створити дозвіл '{permission}': {error}",
|
||||||
"permission_created": "Дозвіл '{permission}' створено",
|
"permission_created": "Дозвіл '{permission}' створено",
|
||||||
"permission_cannot_remove_main": "Видалення основного дозволу заборонено",
|
"permission_cannot_remove_main": "Видалення основного дозволу заборонено",
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
"packages_upgrade_failed": "Не вдалося оновити всі пакети",
|
"packages_upgrade_failed": "Не вдалося оновити всі пакети",
|
||||||
"operation_interrupted": "Операція була перервана вручну?",
|
"operation_interrupted": "Операція була перервана вручну?",
|
||||||
"invalid_number": "Повинно бути число",
|
"invalid_number": "Повинно бути число",
|
||||||
"not_enough_disk_space": "Недостатньо вільного місця на \"{шлях} '.",
|
"not_enough_disk_space": "Недостатньо вільного місця на \"{path} '.",
|
||||||
"migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Інструменти → Міграції на сторінці веб-адміністратора або виконайте команду `yunohost tools migrations run`.",
|
"migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Інструменти → Міграції на сторінці веб-адміністратора або виконайте команду `yunohost tools migrations run`.",
|
||||||
"migrations_success_forward": "Міграція {id} завершена",
|
"migrations_success_forward": "Міграція {id} завершена",
|
||||||
"migrations_skip_migration": "Пропуск міграції {id}...",
|
"migrations_skip_migration": "Пропуск міграції {id}...",
|
||||||
|
@ -175,8 +175,8 @@
|
||||||
"migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...",
|
"migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...",
|
||||||
"migration_0015_specific_upgrade": "Початок поновлення системних пакетів, які повинні бути оновлені незалежно...",
|
"migration_0015_specific_upgrade": "Початок поновлення системних пакетів, які повинні бути оновлені незалежно...",
|
||||||
"migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після поновлення: {manually_modified_files}.",
|
"migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після поновлення: {manually_modified_files}.",
|
||||||
"migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу додатків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.",
|
"migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.",
|
||||||
"migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її додатків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або додатків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.",
|
"migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або застосунків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.",
|
||||||
"migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.",
|
"migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.",
|
||||||
"migration_0015_not_enough_free_space": "Вільного місця в/var/досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.",
|
"migration_0015_not_enough_free_space": "Вільного місця в/var/досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.",
|
||||||
"migration_0015_not_stretch": "Поточний дистрибутив Debian не є Stretch!",
|
"migration_0015_not_stretch": "Поточний дистрибутив Debian не є Stretch!",
|
||||||
|
@ -189,9 +189,9 @@
|
||||||
"migration_ldap_rollback_success": "Система відкотилася.",
|
"migration_ldap_rollback_success": "Система відкотилася.",
|
||||||
"migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... спроба відкату системи.",
|
"migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... спроба відкату системи.",
|
||||||
"migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалої міграцією. Помилка: {error}",
|
"migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалої міграцією. Помилка: {error}",
|
||||||
"migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки додатків перед фактичної міграцією.",
|
"migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки застосунків перед фактичної міграцією.",
|
||||||
"migration_description_0020_ssh_sftp_permissions": "Додайте підтримку дозволів SSH і SFTP",
|
"migration_description_0020_ssh_sftp_permissions": "Додайте підтримку дозволів SSH і SFTP",
|
||||||
"migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами додатків",
|
"migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами застосунків",
|
||||||
"migration_description_0018_xtable_to_nftable": "Перенесення старих правил мережевого трафіку в нову систему nftable",
|
"migration_description_0018_xtable_to_nftable": "Перенесення старих правил мережевого трафіку в нову систему nftable",
|
||||||
"migration_description_0017_postgresql_9p6_to_11": "Перенесення баз даних з PostgreSQL 9.6 на 11",
|
"migration_description_0017_postgresql_9p6_to_11": "Перенесення баз даних з PostgreSQL 9.6 на 11",
|
||||||
"migration_description_0016_php70_to_php73_pools": "Перенесіть php7.0-fpm 'pool' conf файли на php7.3",
|
"migration_description_0016_php70_to_php73_pools": "Перенесіть php7.0-fpm 'pool' conf файли на php7.3",
|
||||||
|
@ -240,9 +240,9 @@
|
||||||
"log_app_config_show_panel": "Показати панель конфігурації програми \"{} '",
|
"log_app_config_show_panel": "Показати панель конфігурації програми \"{} '",
|
||||||
"log_app_action_run": "Активації дії додатка \"{} '",
|
"log_app_action_run": "Активації дії додатка \"{} '",
|
||||||
"log_app_makedefault": "Зробити '{}' додатком за замовчуванням",
|
"log_app_makedefault": "Зробити '{}' додатком за замовчуванням",
|
||||||
"log_app_upgrade": "Оновити додаток '{}'",
|
"log_app_upgrade": "Оновити застосунок '{}'",
|
||||||
"log_app_remove": "Для видалення програми '{}'",
|
"log_app_remove": "Для видалення програми '{}'",
|
||||||
"log_app_install": "Встановіть додаток '{}'",
|
"log_app_install": "Встановіть застосунок '{}'",
|
||||||
"log_app_change_url": "Змініть URL-адресу додатка \"{} '",
|
"log_app_change_url": "Змініть URL-адресу додатка \"{} '",
|
||||||
"log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином",
|
"log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином",
|
||||||
"log_does_exists": "Немає журналу операцій з ім'ям '{log}', використовуйте 'yunohost log list', щоб подивитися всі публічні журнали операцій",
|
"log_does_exists": "Немає журналу операцій з ім'ям '{log}', використовуйте 'yunohost log list', щоб подивитися всі публічні журнали операцій",
|
||||||
|
@ -258,7 +258,7 @@
|
||||||
"hook_name_unknown": "Невідоме ім'я хука '{name}'",
|
"hook_name_unknown": "Невідоме ім'я хука '{name}'",
|
||||||
"hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков",
|
"hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков",
|
||||||
"hook_json_return_error": "Не вдалося розпізнати повернення з хука {path}. Помилка: {msg}. Необроблений контент: {raw_content}",
|
"hook_json_return_error": "Не вдалося розпізнати повернення з хука {path}. Помилка: {msg}. Необроблений контент: {raw_content}",
|
||||||
"hook_exec_not_terminated": "Скрипт не завершився належним чином: {шлях}",
|
"hook_exec_not_terminated": "Скрипт не завершився належним чином: {path}",
|
||||||
"hook_exec_failed": "Не вдалося запустити скрипт: {path}",
|
"hook_exec_failed": "Не вдалося запустити скрипт: {path}",
|
||||||
"group_user_not_in_group": "Користувач {user} не входить в групу {group}",
|
"group_user_not_in_group": "Користувач {user} не входить в групу {group}",
|
||||||
"group_user_already_in_group": "Користувач {user} вже в групі {group}",
|
"group_user_already_in_group": "Користувач {user} вже в групі {group}",
|
||||||
|
@ -297,17 +297,17 @@
|
||||||
"global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора",
|
"global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора",
|
||||||
"global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для веб-сервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
|
"global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для веб-сервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
|
||||||
"global_settings_setting_pop3_enabled": "Включити протокол POP3 для поштового сервера.",
|
"global_settings_setting_pop3_enabled": "Включити протокол POP3 для поштового сервера.",
|
||||||
"global_settings_reset_success": "Попередні настройки тепер збережені в {шлях}.",
|
"global_settings_reset_success": "Попередні настройки тепер збережені в {path}.",
|
||||||
"global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'.",
|
"global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'.",
|
||||||
"global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {причина}",
|
"global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {reason}",
|
||||||
"global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {причина}",
|
"global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {reason}",
|
||||||
"global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {причина}",
|
"global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {reason}",
|
||||||
"global_settings_bad_type_for_setting": "Поганий тип для настройки {setting}, отриманий {received_type}, очікується {expected_type}",
|
"global_settings_bad_type_for_setting": "Поганий тип для настройки {setting}, отриманий {received_type}, очікується {expected_type}",
|
||||||
"global_settings_bad_choice_for_enum": "Поганий вибір для настройки {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}.",
|
"global_settings_bad_choice_for_enum": "Поганий вибір для настройки {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}.",
|
||||||
"firewall_rules_cmd_failed": "Деякі команди правил брандмауера не спрацювали. Більш детальна інформація в журналі.",
|
"firewall_rules_cmd_failed": "Деякі команди правил брандмауера не спрацювали. Більш детальна інформація в журналі.",
|
||||||
"firewall_reloaded": "брандмауер перезавантажений",
|
"firewall_reloaded": "брандмауер перезавантажений",
|
||||||
"firewall_reload_failed": "Не вдалося перезавантажити брандмауер",
|
"firewall_reload_failed": "Не вдалося перезавантажити брандмауер",
|
||||||
"file_does_not_exist": "Файл {шлях} не існує.",
|
"file_does_not_exist": "Файл {path} не існує.",
|
||||||
"field_invalid": "Неприпустиме поле '{}'",
|
"field_invalid": "Неприпустиме поле '{}'",
|
||||||
"experimental_feature": "Попередження: Ця функція є експериментальною і не вважається стабільною, ви не повинні використовувати її, якщо не знаєте, що робите.",
|
"experimental_feature": "Попередження: Ця функція є експериментальною і не вважається стабільною, ви не повинні використовувати її, якщо не знаєте, що робите.",
|
||||||
"extracting": "Витяг...",
|
"extracting": "Витяг...",
|
||||||
|
@ -321,8 +321,8 @@
|
||||||
"dyndns_key_generating": "Генерація DNS-ключа... Це може зайняти деякий час.",
|
"dyndns_key_generating": "Генерація DNS-ключа... Це може зайняти деякий час.",
|
||||||
"dyndns_ip_updated": "Оновлення свій IP-адресу в DynDNS",
|
"dyndns_ip_updated": "Оновлення свій IP-адресу в DynDNS",
|
||||||
"dyndns_ip_update_failed": "Не вдалося оновити IP-адреса в DynDNS",
|
"dyndns_ip_update_failed": "Не вдалося оновити IP-адреса в DynDNS",
|
||||||
"dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {домен} на {провайдера}.",
|
"dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {domain} на {provider}.",
|
||||||
"dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {провайдер} надати {домен}.",
|
"dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {provider} надати {domain}.",
|
||||||
"dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів).",
|
"dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів).",
|
||||||
"dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, підключившись через SSH і виконавши `sudo apt install --fix-broken` і/або `sudo dpkg --configure -a`.",
|
"dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, підключившись через SSH і виконавши `sudo apt install --fix-broken` і/або `sudo dpkg --configure -a`.",
|
||||||
"downloading": "Завантаження…",
|
"downloading": "Завантаження…",
|
||||||
|
@ -331,7 +331,7 @@
|
||||||
"domain_unknown": "невідомий домен",
|
"domain_unknown": "невідомий домен",
|
||||||
"domain_name_unknown": "Домен '{domain}' невідомий",
|
"domain_name_unknown": "Домен '{domain}' невідомий",
|
||||||
"domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.",
|
"domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.",
|
||||||
"domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих додатків: {apps} Ви впевнені, що хочете це зробити? [{Відповіді}].",
|
"domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих застосунків: {apps} Ви впевнені, що хочете це зробити? [{answers}].",
|
||||||
"domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).",
|
"domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).",
|
||||||
"domain_exists": "Домен вже існує",
|
"domain_exists": "Домен вже існує",
|
||||||
"domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS",
|
"domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS",
|
||||||
|
@ -367,7 +367,7 @@
|
||||||
"diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на <a href='https://yunohost.org/dns_local_network'> https://yunohost.org/dns_local_network </a>.",
|
"diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на <a href='https://yunohost.org/dns_local_network'> https://yunohost.org/dns_local_network </a>.",
|
||||||
"diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.",
|
"diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.",
|
||||||
"diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в <a href='https://yunohost.org/isp_box_config'> https://yunohost.org/isp_box_config</ a>.",
|
"diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в <a href='https://yunohost.org/isp_box_config'> https://yunohost.org/isp_box_config</ a>.",
|
||||||
"diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {категорії} (служба {сервіс}).",
|
"diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {category} (служба {service}).",
|
||||||
"diagnosis_ports_ok": "Порт {port} доступний ззовні.",
|
"diagnosis_ports_ok": "Порт {port} доступний ззовні.",
|
||||||
"diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.",
|
"diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.",
|
||||||
"diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.",
|
"diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.",
|
||||||
|
@ -383,8 +383,8 @@
|
||||||
"diagnosis_description_basesystem": "Базова система",
|
"diagnosis_description_basesystem": "Базова система",
|
||||||
"diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро linux (або звернутися до вашого серверного провайдеру, якщо це не спрацює). Додаткову інформацію див. На сайті https://meltdownattack.com/.",
|
"diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро linux (або звернутися до вашого серверного провайдеру, якщо це не спрацює). Додаткову інформацію див. На сайті https://meltdownattack.com/.",
|
||||||
"diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви уразливі до критичної уразливості безпеки Meltdown.",
|
"diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви уразливі до критичної уразливості безпеки Meltdown.",
|
||||||
"diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {простір}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.",
|
"diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.",
|
||||||
"diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {простір}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.",
|
"diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.",
|
||||||
"diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди <cmd> yunohost tools regen-conf {category} --dry-run --with-diff </ cmd> і примусово повернути рекомендовану конфігурацію за допомогою <cmd> yunohost tools regen- conf {category} --force </ cmd>.",
|
"diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди <cmd> yunohost tools regen-conf {category} --dry-run --with-diff </ cmd> і примусово повернути рекомендовану конфігурацію за допомогою <cmd> yunohost tools regen- conf {category} --force </ cmd>.",
|
||||||
"diagnosis_regenconf_manually_modified": "Конфігураційний файл <code> {file} </ code>, схоже, був змінений вручну.",
|
"diagnosis_regenconf_manually_modified": "Конфігураційний файл <code> {file} </ code>, схоже, був змінений вручну.",
|
||||||
"diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!",
|
"diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!",
|
||||||
|
@ -393,7 +393,7 @@
|
||||||
"diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі",
|
"diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі",
|
||||||
"diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах",
|
"diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах",
|
||||||
"diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.",
|
"diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.",
|
||||||
"diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {причина}",
|
"diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}",
|
||||||
"diagnosis_mail_blacklist_listed_by": "Ваш IP або домен <code> {item} </ code> знаходиться в чорному списку {blacklist_name}.",
|
"diagnosis_mail_blacklist_listed_by": "Ваш IP або домен <code> {item} </ code> знаходиться в чорному списку {blacklist_name}.",
|
||||||
"diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список",
|
"diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список",
|
||||||
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: <code> {rdns_domain} </ code> <br> Очікуване значення: <code> {ehlo_domain} </ code>.",
|
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: <code> {rdns_domain} </ code> <br> Очікуване значення: <code> {ehlo_domain} </ code>.",
|
||||||
|
@ -439,11 +439,11 @@
|
||||||
"updating_apt_cache": "Вибірка доступних оновлень для системних пакетів...",
|
"updating_apt_cache": "Вибірка доступних оновлень для системних пакетів...",
|
||||||
"update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}",
|
"update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}",
|
||||||
"update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}",
|
"update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}",
|
||||||
"unrestore_app": "{App} не буде поновлено",
|
"unrestore_app": "{app} не буде поновлено",
|
||||||
"unlimit": "немає квоти",
|
"unlimit": "немає квоти",
|
||||||
"unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.",
|
"unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.",
|
||||||
"unexpected_error": "Щось пішло не так: {error}",
|
"unexpected_error": "Щось пішло не так: {error}",
|
||||||
"unbackup_app": "{App} НЕ буде збережений",
|
"unbackup_app": "{app} НЕ буде збережений",
|
||||||
"tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено. Натисніть [Enter] для повернення командного рядка",
|
"tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено. Натисніть [Enter] для повернення командного рядка",
|
||||||
"tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у фоновому режимі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в веб-адмін. Журнал поновлення буде доступний в Інструменти → Журнал (в веб-адміном) або за допомогою 'yunohost log list' (з командного рядка).",
|
"tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у фоновому режимі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в веб-адмін. Журнал поновлення буде доступний в Інструменти → Журнал (в веб-адміном) або за допомогою 'yunohost log list' (з командного рядка).",
|
||||||
"tools_upgrade_special_packages": "Тепер оновлюємо \"спеціальні\" (пов'язані з yunohost) пакети…",
|
"tools_upgrade_special_packages": "Тепер оновлюємо \"спеціальні\" (пов'язані з yunohost) пакети…",
|
||||||
|
@ -467,12 +467,12 @@
|
||||||
"service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}",
|
"service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}",
|
||||||
"diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблокований).",
|
"diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблокований).",
|
||||||
"diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує своп на SD-карті або SSD-накопичувачі, це може різко скоротити термін служби устройства`.",
|
"diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує своп на SD-карті або SSD-накопичувачі, це може різко скоротити термін служби устройства`.",
|
||||||
"diagnosis_swap_ok": "Система має {усього} свопу!",
|
"diagnosis_swap_ok": "Система має {total} свопу!",
|
||||||
"diagnosis_swap_notsomuch": "Система має тільки {усього} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {рекомендованого} обсягу підкачки.",
|
"diagnosis_swap_notsomuch": "Система має тільки {total} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {recommended} обсягу підкачки.",
|
||||||
"diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {рекомендованого} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.",
|
"diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {recommended} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.",
|
||||||
"diagnosis_ram_ok": "Система все ще має {доступно} ({доступний_процент}%) оперативної пам'яті з {усього}.",
|
"diagnosis_ram_ok": "Система все ще має {available} ({available_percent}%) оперативної пам'яті з {total}.",
|
||||||
"diagnosis_ram_low": "У системі є {доступно} ({доступний_процент}%) оперативної пам'яті (з {усього}). Будьте уважні.",
|
"diagnosis_ram_low": "У системі є {available} ({available_percent}%) оперативної пам'яті (з {total}). Будьте уважні.",
|
||||||
"diagnosis_ram_verylow": "Система має тільки {доступне} ({доступний_процент}%) оперативної пам'яті! (З {усього})",
|
"diagnosis_ram_verylow": "Система має тільки {available} ({available_percent}%) оперативної пам'яті! (З {total})",
|
||||||
"diagnosis_diskusage_ok": "У сховищі <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) залишилося {free} ({free_percent}%) вільного місця (з {total})!",
|
"diagnosis_diskusage_ok": "У сховищі <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) залишилося {free} ({free_percent}%) вільного місця (з {total})!",
|
||||||
"diagnosis_diskusage_low": "Сховище <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.",
|
"diagnosis_diskusage_low": "Сховище <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.",
|
||||||
"diagnosis_diskusage_verylow": "Сховище <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!",
|
"diagnosis_diskusage_verylow": "Сховище <code> {mountpoint} </ code> (на пристрої <code> {device} </ code>) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!",
|
||||||
|
@ -480,162 +480,162 @@
|
||||||
"diagnosis_services_bad_status": "Сервіс {service} знаходиться в {status} :(",
|
"diagnosis_services_bad_status": "Сервіс {service} знаходиться в {status} :(",
|
||||||
"diagnosis_services_conf_broken": "Конфігурація порушена для служби {service}!",
|
"diagnosis_services_conf_broken": "Конфігурація порушена для служби {service}!",
|
||||||
"diagnosis_services_running": "Служба {service} запущена!",
|
"diagnosis_services_running": "Служба {service} запущена!",
|
||||||
"diagnosis_domain_expires_in": "Термін дії {домену} закінчується через {днів} днів.",
|
"diagnosis_domain_expires_in": "Термін дії {domain} закінчується через {days} днів.",
|
||||||
"diagnosis_domain_expiration_error": "Термін дії деяких доменів закінчується ДУЖЕ СКОРО!",
|
"diagnosis_domain_expiration_error": "Термін дії деяких доменів закінчується ДУЖЕ СКОРО!",
|
||||||
"diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!",
|
"diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!",
|
||||||
"diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.",
|
"diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.",
|
||||||
"diagnosis_domain_expiration_not_found_details": "Інформація WHOIS для домену {domain} не містить інформації про термін дії?",
|
"diagnosis_domain_expiration_not_found_details": "Інформація WHOIS для домену {domain} не містить інформації про термін дії?",
|
||||||
"diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або термін його дії закінчився!",
|
"diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або строк його дії сплив!",
|
||||||
"diagnosis_domain_expiration_not_found": "Неможливо перевірити термін дії деяких доменів",
|
"diagnosis_domain_expiration_not_found": "Неможливо перевірити строк дії деяких доменів",
|
||||||
"diagnosis_dns_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) і тому не очікується, що у нього будуть актуальні записи DNS.",
|
"diagnosis_dns_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) і тому не очікується, що у нього будуть актуальні записи DNS.",
|
||||||
"diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди <cmd> yunohost dyndns update --force </ cmd>.",
|
"diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди <cmd>yunohost dyndns update --force</cmd>.",
|
||||||
"diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті <a href='https://yunohost.org/dns_config'> https://yunohost.org/dns_config </a>.",
|
"diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a>.",
|
||||||
"diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованої конфігурації: <br> Type: <code> {type} </ code> <br> Name: <code> {name} </ code> <br> Поточне значення: <code> {current} </ code> <br> Очікуване значення: <code> {value} </ code>",
|
"diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованій конфігурації: <br>Тип: <code>{type}</code><br>Назва: <code>{name}</code><br>Поточне значення: <code>{current}</code><br>Очікуване значення: <code>{value}</code>",
|
||||||
"diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступною інформацією. <br> Тип: <code> {type} </ code> <br> Name: <code> {name} </ code> <br> Value: < code> {value} </ code>.",
|
"diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступними відомостями.\n<br>Тип: <code>{type}</code>\n<br>Назва: <code>{name}</code>\n<br>Значення: <code>{value}</code>",
|
||||||
"diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або невірні для домену {домен} (категорія {категорія})",
|
"diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або неправильні для домену {domain} (категорія {category})",
|
||||||
"diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {домен} (категорія {категорія})",
|
"diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {domain} (категорія {category})",
|
||||||
"diagnosis_ip_weird_resolvconf_details": "Файл <code> /etc/resolv.conf </ code> повинен бути симлінк на <code> /etc/resolvconf/run/resolv.conf </ code>, що вказує на <code> 127.0.0.1 </ code> (dnsmasq ). Якщо ви хочете вручну налаштувати DNS Резолвер, відредагуйте <code> /etc/resolv.dnsmasq.conf </ code>.",
|
"diagnosis_ip_weird_resolvconf_details": "Файл <code>/etc/resolv.conf</code> повинен бути символічним посиланням на <code>/etc/resolvconf/run/resolv.conf</code>, що вказує на <code>127.0.0.1</code>(dnsmasq). Якщо ви хочете вручну налаштувати DNS вирішувачі (resolvers), відредагуйте <code>/etc/resolv.dnsmasq.conf</code>.",
|
||||||
"diagnosis_ip_weird_resolvconf": "Дозвіл DNS, схоже, працює, але схоже, що ви використовуєте для користувача <code> /etc/resolv.conf </ code>.",
|
"diagnosis_ip_weird_resolvconf": "Роздільність DNS, схоже, працює, але схоже, що ви використовуєте користувацьку <code>/etc/resolv.conf</code>.",
|
||||||
"diagnosis_ip_broken_resolvconf": "Схоже, що дозвіл доменних імен на вашому сервері порушено, що пов'язано з тим, що <code> /etc/resolv.conf </ code> не вказує на <code> 127.0.0.1 </ code>.",
|
"diagnosis_ip_broken_resolvconf": "Схоже, що роздільність доменних імен на вашому сервері порушено, що пов'язано з тим, що <code>/etc/resolv.conf</code> не вказує на <code>127.0.0.1</code>.",
|
||||||
"diagnosis_ip_broken_dnsresolution": "Дозвіл доменних імен, схоже, з якоїсь причини не працює... Брандмауер блокує DNS-запити?",
|
"diagnosis_ip_broken_dnsresolution": "Роздільність доменних імен, схоже, з якоїсь причини не працює... Фаєрвол блокує DNS-запити?",
|
||||||
"diagnosis_ip_dnsresolution_working": "Дозвіл доменних імен працює!",
|
"diagnosis_ip_dnsresolution_working": "Роздільність доменних імен працює!",
|
||||||
"diagnosis_ip_not_connected_at_all": "Здається, що сервер взагалі не підключений до Інтернету !?",
|
"diagnosis_ip_not_connected_at_all": "Здається, сервер взагалі не під'єднаний до Інтернету!?",
|
||||||
"diagnosis_ip_local": "Локальний IP: <code> {local} </ code>.",
|
"diagnosis_ip_local": "Локальний IP: <code>{local}</code>.",
|
||||||
"diagnosis_ip_global": "Глобальний IP: <code> {global} </ code>",
|
"diagnosis_ip_global": "Глобальний IP: <code>{global}</code>",
|
||||||
"diagnosis_ip_no_ipv6_tip": "Наявність працюючого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: <a href='https://yunohost.org/#/ipv6'> https://yunohost.org/#/ipv6</ a>. Якщо ви не можете включити IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо ігнорувати це попередження.",
|
"diagnosis_ip_no_ipv6_tip": "Наявність робочого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: <a href='https://yunohost.org/#/ipv6'> https://yunohost.org/#/ipv6</a>. Якщо ви не можете увімкнути IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо нехтувати цим попередженням.",
|
||||||
"diagnosis_ip_no_ipv6": "Сервер не має працюючого IPv6.",
|
"diagnosis_ip_no_ipv6": "Сервер не має робочого IPv6.",
|
||||||
"diagnosis_ip_connected_ipv6": "Сервер підключений до Інтернету через IPv6!",
|
"diagnosis_ip_connected_ipv6": "Сервер під'єднаний до Інтернету через IPv6!",
|
||||||
"diagnosis_ip_no_ipv4": "Сервер не має працюючого IPv4.",
|
"diagnosis_ip_no_ipv4": "Сервер не має робочого IPv4.",
|
||||||
"diagnosis_ip_connected_ipv4": "Сервер підключений до Інтернету через IPv4!",
|
"diagnosis_ip_connected_ipv4": "Сервер під'єднаний до Інтернету через IPv4!",
|
||||||
"diagnosis_no_cache": "Для категорії \"{категорія} 'ще немає кеша діагнозів.",
|
"diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагностики.",
|
||||||
"diagnosis_failed": "Не вдалося результат діагностики для категорії '{категорія}': {error}",
|
"diagnosis_failed": "Не вдалося отримати результат діагностики для категорії '{category}': {error}",
|
||||||
"diagnosis_everything_ok": "Все виглядає добре для {категорії}!",
|
"diagnosis_everything_ok": "Усе виглядає добре для {category}!",
|
||||||
"diagnosis_found_warnings": "Знайдено {попередження} пунктів, які можна поліпшити для {категорії}.",
|
"diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.",
|
||||||
"diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {попередження} попередження (я)), що відносяться до {category}!",
|
"diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {warnings} попередження (я)), що відносяться до {category}!",
|
||||||
"diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!",
|
"diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!",
|
||||||
"diagnosis_ignored_issues": "(+ {Nb_ignored} проігнорована проблема (проблеми))",
|
"diagnosis_ignored_issues": "(+ {nb_ignored} знехтувана проблема (проблеми))",
|
||||||
"diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {категорії}, поки є важливі проблеми, пов'язані з {глибиною}.",
|
"diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {category}, поки є важливі проблеми, пов'язані з {dep}.",
|
||||||
"diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {категорії}. Повторна діагностика поки не проводиться!)",
|
"diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {category}. Повторна діагностика поки не проводиться!)",
|
||||||
"diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{категорія}': {error}",
|
"diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{category}': {error}",
|
||||||
"diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Diagnosis в веб-адміном або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.",
|
"diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Діагностика в вебадміністраторі або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.",
|
||||||
"diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені з стороннього сховища під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили додатки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити цю ситуацію, спробуйте виконати наступну команду: <cmd> {cmd_to_fix} </ cmd>.",
|
"diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені зі стороннього репозиторію під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили застосунки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити це становище, спробуйте виконати наступну команду: <cmd>{cmd_to_fix}</cmd>.",
|
||||||
"diagnosis_package_installed_from_sury": "Деякі системні пакети повинні бути знижені в статусі",
|
"diagnosis_package_installed_from_sury": "Деякі системні пакети мають бути зістарені у версії",
|
||||||
"diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання сховища backports. Якщо ви не знаєте, що робите, ми настійно не рекомендуємо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.",
|
"diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання репозиторія backports. Якщо ви не знаєте, що робите, ми наполегливо не радимо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.",
|
||||||
"diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдалий або часткового оновлення.",
|
"diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдале або часткове оновлення.",
|
||||||
"diagnosis_basesystem_ynh_main_version": "Сервер працює під управлінням YunoHost {main_version} ({repo})",
|
"diagnosis_basesystem_ynh_main_version": "Сервер працює під управлінням YunoHost {main_version} ({repo})",
|
||||||
"diagnosis_basesystem_ynh_single_version": "{Пакет} версія: {версія} ({repo})",
|
"diagnosis_basesystem_ynh_single_version": "{package} версія: {version} ({repo})",
|
||||||
"diagnosis_basesystem_kernel": "Сервер працює під управлінням ядра Linux {kernel_version}",
|
"diagnosis_basesystem_kernel": "Сервер працює під управлінням ядра Linux {kernel_version}",
|
||||||
"diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}",
|
"diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}",
|
||||||
"diagnosis_basesystem_hardware_model": "Модель сервера - {model}",
|
"diagnosis_basesystem_hardware_model": "Модель сервера - {model}",
|
||||||
"diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}",
|
"diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}",
|
||||||
"custom_app_url_required": "Ви повинні надати URL для оновлення вашого призначеного для користувача додатки {app}.",
|
"custom_app_url_required": "Ви повинні надати URL-адресу для оновлення вашого користувацького застосунку {app}.",
|
||||||
"confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Ця програма не входить в каталог додатків YunoHost. Установлення сторонніх додатків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.",
|
"confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить в каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.",
|
||||||
"confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що це додаток все ще експериментальне (якщо не сказати, що воно явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.",
|
"confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що цей застосунок все ще експериментальний (якщо не сказати, що він явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.",
|
||||||
"confirm_app_install_warning": "Попередження: Ця програма може працювати, але не дуже добре інтегровано в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{Відповіді}]. ",
|
"confirm_app_install_warning": "Попередження: Цей застосунок може працювати, але він не дуже добре інтегрований в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ",
|
||||||
"certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати ім'я самоподпісивающегося центру (файл: {file})",
|
"certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати назву самопідписного центру (файл: {file})",
|
||||||
"certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самоподпісивающегося центру (файл: {file})",
|
"certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самопідписного центру (файл: {file})",
|
||||||
"certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})",
|
"certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})",
|
||||||
"certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. Https://letsencrypt.org/docs/rate-limits/ для отримання більш докладної інформації.",
|
"certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. https://letsencrypt.org/docs/rate-limits/ для отримання подробиць.",
|
||||||
"certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain} \"не дозволяється на той же IP-адресу, що і' {domain} '. Деякі функції будуть недоступні, поки ви не виправите це і не перегенеріруете сертифікат.",
|
"certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain}' не дозволяється на тій же IP-адресі, що і '{domain}'. Деякі функції будуть недоступні, поки ви не виправите це і не перестворите сертифікат.",
|
||||||
"certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Web' в діагностиці для отримання додаткової інформації. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).",
|
"certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Мережа' в діагностиці для отримання додаткових даних. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).",
|
||||||
"certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткової інформації. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки вона пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).",
|
"certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткових даних. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки він пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).",
|
||||||
"certmanager_domain_cert_not_selfsigned": "Сертифікат для домену {domain} не є самоподпісанного. Ви впевнені, що хочете замінити його? (Для цього використовуйте '--force').",
|
"certmanager_domain_cert_not_selfsigned": "Сертифікат для домену {domain} не є самопідписаним. Ви впевнені, що хочете замінити його? (Для цього використовуйте '--force').",
|
||||||
"certmanager_domain_not_diagnosed_yet": "Поки немає результатів діагностики для домену {domain}. Будь ласка, повторно проведіть діагностику для категорій 'DNS-записи' і 'Web' в розділі діагностики, щоб перевірити, чи готовий домен до Let's Encrypt. (Або, якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).",
|
"certmanager_domain_not_diagnosed_yet": "Поки немає результатів діагностики для домену {domain}. Будь ласка, повторно проведіть діагностику для категорій 'DNS-записи' і 'Мережа' в розділі діагностики, щоб перевірити, чи готовий домен до Let's Encrypt. (Або, якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).",
|
||||||
"certmanager_certificate_fetching_or_enabling_failed": "Спроба використовувати новий сертифікат для {domain} не спрацювала...",
|
"certmanager_certificate_fetching_or_enabling_failed": "Спроба використовувати новий сертифікат для {domain} не спрацювала...",
|
||||||
"certmanager_cert_signing_failed": "Не вдалося підписати новий сертифікат",
|
"certmanager_cert_signing_failed": "Не вдалося підписати новий сертифікат",
|
||||||
"certmanager_cert_renew_success": "Сертифікат Let's Encrypt оновлений для домену '{domain}'",
|
"certmanager_cert_renew_success": "Сертифікат Let's Encrypt оновлений для домену '{domain}'",
|
||||||
"certmanager_cert_install_success_selfsigned": "Самоподпісанний сертифікат тепер встановлений для домену '{domain}'",
|
"certmanager_cert_install_success_selfsigned": "Самопідписаний сертифікат тепер встановлений для домену '{domain}'",
|
||||||
"certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домену '{domain}'",
|
"certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домена '{domain}'",
|
||||||
"certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домену {domain} (файл: {файл}), причина: {причина}",
|
"certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домена {domain} (файл: {file}), причина: {reason}",
|
||||||
"certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший і дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)",
|
"certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)",
|
||||||
"certmanager_attempt_to_renew_valid_cert": "Термін дії сертифіката для домену '{domain} \"не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)",
|
"certmanager_attempt_to_renew_valid_cert": "Строк дії сертифіката для домена '{domain}' не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)",
|
||||||
"certmanager_attempt_to_renew_nonLE_cert": "Сертифікат для домену '{domain}' не випущено Let's Encrypt. Неможливо продовжити його автоматично!",
|
"certmanager_attempt_to_renew_nonLE_cert": "Сертифікат для домену '{domain}' не випущено Let's Encrypt. Неможливо продовжити його автоматично!",
|
||||||
"certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущена для {domain} прямо зараз, тому що в його nginx conf відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run - with-diff`.",
|
"certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущене для {domain} прямо зараз, тому що в його nginx-конфігурації відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run --with-diff`.",
|
||||||
"backup_with_no_restore_script_for_app": "{App} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього додатка.",
|
"backup_with_no_restore_script_for_app": "{app} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього застосунку.",
|
||||||
"backup_with_no_backup_script_for_app": "Додаток '{app}' не має скрипта резервного копіювання. Ігнорування.",
|
"backup_with_no_backup_script_for_app": "Застосунок '{app}' не має скрипта резервного копіювання. Нехтую ним.",
|
||||||
"backup_unable_to_organize_files": "Неможливо використовувати швидкий метод для організації файлів в архіві",
|
"backup_unable_to_organize_files": "Неможливо використовувати швидкий спосіб для організації файлів в архіві",
|
||||||
"backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.",
|
"backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.",
|
||||||
"backup_running_hooks": "Запуск гачків резервного копіювання...",
|
"backup_running_hooks": "Запуск гачків (hook) резервного копіювання...",
|
||||||
"backup_permission": "Дозвіл на резервне копіювання для {app}",
|
"backup_permission": "Дозвіл на резервне копіювання для {app}",
|
||||||
"backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є непрацюючою симлінк. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.",
|
"backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є неробочим символічним посиланням. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.",
|
||||||
"backup_output_directory_required": "Ви повинні вказати вихідний каталог для резервного копіювання",
|
"backup_output_directory_required": "Ви повинні вказати вихідний каталог для резервного копіювання",
|
||||||
"backup_output_directory_not_empty": "Ви повинні вибрати порожній вихідний каталог",
|
"backup_output_directory_not_empty": "Ви повинні вибрати порожній вихідний каталог",
|
||||||
"backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах/bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.",
|
"backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах /bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.",
|
||||||
"backup_nothings_done": "нічого зберігати",
|
"backup_nothings_done": "Нема що зберігати",
|
||||||
"backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву",
|
"backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву",
|
||||||
"backup_mount_archive_for_restore": "Підготовка архіву для відновлення...",
|
"backup_mount_archive_for_restore": "Підготовлення архіву для відновлення...",
|
||||||
"backup_method_tar_finished": "Створено архів резервного копіювання TAR",
|
"backup_method_tar_finished": "Створено архів резервного копіювання TAR",
|
||||||
"backup_method_custom_finished": "Призначений для користувача метод резервного копіювання '{метод}' завершено",
|
"backup_method_custom_finished": "Користувацький спосіб резервного копіювання '{method}' завершено",
|
||||||
"backup_method_copy_finished": "Резервне копіювання завершено",
|
"backup_method_copy_finished": "Резервне копіювання завершено",
|
||||||
"backup_hook_unknown": "Гачок резервного копіювання '{hook}' невідомий",
|
"backup_hook_unknown": "Гачок (hook) резервного копіювання '{hook}' невідомий",
|
||||||
"backup_deleted": "Резервна копія видалена",
|
"backup_deleted": "Резервна копія видалена",
|
||||||
"backup_delete_error": "Не вдалося видалити '{path}'",
|
"backup_delete_error": "Не вдалося видалити '{path}'",
|
||||||
"backup_custom_mount_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'монтування'",
|
"backup_custom_mount_error": "Користувацький спосіб резервного копіювання не зміг пройти етап 'монтування'",
|
||||||
"backup_custom_backup_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'резервне копіювання'",
|
"backup_custom_backup_error": "Користувацький спосіб резервного копіювання не зміг пройти етап 'резервне копіювання'",
|
||||||
"backup_csv_creation_failed": "Не вдалося створити CSV-файл, необхідний для відновлення",
|
"backup_csv_creation_failed": "Не вдалося створити CSV-файл, необхідний для відновлення",
|
||||||
"backup_csv_addition_failed": "Не вдалося додати файли для резервного копіювання в CSV-файл",
|
"backup_csv_addition_failed": "Не вдалося додати файли для резервного копіювання в CSV-файл",
|
||||||
"backup_creation_failed": "Не вдалося створити архів резервного копіювання",
|
"backup_creation_failed": "Не вдалося створити архів резервного копіювання",
|
||||||
"backup_create_size_estimation": "Архів буде містити близько {розмір} даних.",
|
"backup_create_size_estimation": "Архів буде містити близько {size} даних.",
|
||||||
"backup_created": "Резервна копія створена",
|
"backup_created": "Резервна копія створена",
|
||||||
"backup_couldnt_bind": "Не вдалося зв'язати {src} з {dest}.",
|
"backup_couldnt_bind": "Не вдалося зв'язати {src} з {dest}.",
|
||||||
"backup_copying_to_organize_the_archive": "Копіювання {size} MB для організації архіву",
|
"backup_copying_to_organize_the_archive": "Копіювання {size} МБ для організації архіву",
|
||||||
"backup_cleaning_failed": "Не вдалося очистити тимчасову папку резервного копіювання",
|
"backup_cleaning_failed": "Не вдалося очистити тимчасовий каталог резервного копіювання",
|
||||||
"backup_cant_mount_uncompress_archive": "Не вдалося змонтувати нестислий архів як захищений від запису",
|
"backup_cant_mount_uncompress_archive": "Не вдалося змонтувати нестислий архів як захищений від запису",
|
||||||
"backup_ask_for_copying_if_needed": "Чи хочете ви тимчасово виконати резервне копіювання з використанням {size} MB? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені більш ефективним методом).",
|
"backup_ask_for_copying_if_needed": "Ви бажаєте тимчасово виконати резервне копіювання з використанням {size} МБ? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені дієвіше).",
|
||||||
"backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'.",
|
"backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'.",
|
||||||
"backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервної копії",
|
"backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервній копії",
|
||||||
"backup_archive_corrupted": "Схоже, що архів резервної копії \"{archive} 'пошкоджений: {error}",
|
"backup_archive_corrupted": "Схоже, що архів резервної копії '{archive}' пошкоджений: {error}",
|
||||||
"backup_archive_cant_retrieve_info_json": "Не вдалося завантажити інформацію для архіву '{archive}'... info.json не може бути отриманий (або не є коректним json).",
|
"backup_archive_cant_retrieve_info_json": "Не вдалося завантажити відомості для архіву '{archive}'... info.json не може бути отриманий(або не є правильним json).",
|
||||||
"backup_archive_open_failed": "Не вдалося відкрити архів резервних копій",
|
"backup_archive_open_failed": "Не вдалося відкрити архів резервної копії",
|
||||||
"backup_archive_name_unknown": "Невідомий локальний архів резервного копіювання з ім'ям '{name}'",
|
"backup_archive_name_unknown": "Невідомий локальний архів резервного копіювання з назвою '{name}'",
|
||||||
"backup_archive_name_exists": "Архів резервного копіювання з таким ім'ям вже існує.",
|
"backup_archive_name_exists": "Архів резервного копіювання з такою назвою вже існує.",
|
||||||
"backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (непрацююча посилання на {path})",
|
"backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (неробоче посилання на {path})",
|
||||||
"backup_archive_app_not_found": "Не вдалося знайти {app} в архіві резервного копіювання",
|
"backup_archive_app_not_found": "Не вдалося знайти {app} в архіві резервного копіювання",
|
||||||
"backup_applying_method_tar": "Створення резервного TAR-архіву...",
|
"backup_applying_method_tar": "Створення резервного TAR-архіву...",
|
||||||
"backup_applying_method_custom": "Виклик для користувача методу резервного копіювання '{метод}'...",
|
"backup_applying_method_custom": "Виклик користувацького способу резервного копіювання '{method}'...",
|
||||||
"backup_applying_method_copy": "Копіювання всіх файлів в резервну копію...",
|
"backup_applying_method_copy": "Копіювання всіх файлів у резервну копію...",
|
||||||
"backup_app_failed": "Не вдалося створити резервну копію {app}",
|
"backup_app_failed": "Не вдалося створити резервну копію {app}",
|
||||||
"backup_actually_backuping": "Створення резервного архіву з зібраних файлів...",
|
"backup_actually_backuping": "Створення резервного архіву з зібраних файлів...",
|
||||||
"backup_abstract_method": "Цей метод резервного копіювання ще не реалізований",
|
"backup_abstract_method": "Цей спосіб резервного копіювання ще не реалізований",
|
||||||
"ask_password": "пароль",
|
"ask_password": "Пароль",
|
||||||
"ask_new_path": "Новий шлях",
|
"ask_new_path": "Новий шлях",
|
||||||
"ask_new_domain": "новий домен",
|
"ask_new_domain": "Новий домен",
|
||||||
"ask_new_admin_password": "Новий адміністративний пароль",
|
"ask_new_admin_password": "Новий пароль адміністратора",
|
||||||
"ask_main_domain": "основний домен",
|
"ask_main_domain": "Основний домен",
|
||||||
"ask_lastname": "Прізвище",
|
"ask_lastname": "Прізвище",
|
||||||
"ask_firstname": "ім'я",
|
"ask_firstname": "Ім'я",
|
||||||
"ask_user_domain": "Домен для адреси електронної пошти користувача і облікового запису XMPP",
|
"ask_user_domain": "Домен для адреси е-пошти користувача і облікового запису XMPP",
|
||||||
"apps_catalog_update_success": "Каталог додатків був оновлений!",
|
"apps_catalog_update_success": "Каталог застосунків був оновлений!",
|
||||||
"apps_catalog_obsolete_cache": "Кеш каталогу додатків порожній або застарів.",
|
"apps_catalog_obsolete_cache": "Кеш каталогу застосунків порожній або застарів.",
|
||||||
"apps_catalog_failed_to_download": "Неможливо завантажити каталог додатків {apps_catalog}: {error}",
|
"apps_catalog_failed_to_download": "Неможливо завантажити каталог застосунків {apps_catalog}: {error}",
|
||||||
"apps_catalog_updating": "Оновлення каталогу додатків…",
|
"apps_catalog_updating": "Оновлення каталогу застосунків…",
|
||||||
"apps_catalog_init_success": "Система каталогу додатків инициализирована!",
|
"apps_catalog_init_success": "Систему каталогу застосунків ініціалізовано!",
|
||||||
"apps_already_up_to_date": "Всі додатки вже оновлені",
|
"apps_already_up_to_date": "Усі застосунки вже оновлено",
|
||||||
"app_packaging_format_not_supported": "Ця програма не може бути встановлено, тому що формат його упаковки не підтримується вашою версією YunoHost. Можливо, вам слід оновити вашу систему.",
|
"app_packaging_format_not_supported": "Цей застосунок не може бути встановлено, тому що формат його упакування не підтримується вашою версією YunoHost. Можливо, вам слід оновити систему.",
|
||||||
"app_upgraded": "{App} оновлено",
|
"app_upgraded": "{app} оновлено",
|
||||||
"app_upgrade_some_app_failed": "Деякі програми не можуть бути оновлені",
|
"app_upgrade_some_app_failed": "Деякі застосунки не можуть бути оновлені",
|
||||||
"app_upgrade_script_failed": "Сталася помилка в сценарії оновлення програми",
|
"app_upgrade_script_failed": "Сталася помилка в скрипті оновлення застосунку",
|
||||||
"app_upgrade_failed": "Не вдалося оновити {app}: {error}",
|
"app_upgrade_failed": "Не вдалося оновити {app}: {error}",
|
||||||
"app_upgrade_app_name": "Зараз оновлюємо {app}...",
|
"app_upgrade_app_name": "Зараз оновлюємо {app}...",
|
||||||
"app_upgrade_several_apps": "Наступні додатки будуть оновлені: {apps}",
|
"app_upgrade_several_apps": "Наступні застосунки буде оновлено: {apps}",
|
||||||
"app_unsupported_remote_type": "Для додатка використовується непідтримуваний віддалений тип.",
|
"app_unsupported_remote_type": "Для застосунку використовується непідтримуваний віддалений тип.",
|
||||||
"app_unknown": "невідоме додаток",
|
"app_unknown": "Невідомий застосунок",
|
||||||
"app_start_restore": "Відновлення {app}...",
|
"app_start_restore": "Відновлення {app}...",
|
||||||
"app_start_backup": "Збір файлів для резервного копіювання {app}...",
|
"app_start_backup": "Збирання файлів для резервного копіювання {app}...",
|
||||||
"app_start_remove": "Видалення {app}...",
|
"app_start_remove": "Вилучення {app}...",
|
||||||
"app_start_install": "Установлення {app}...",
|
"app_start_install": "Установлення {app}...",
|
||||||
"app_sources_fetch_failed": "Не вдалося вихідні файли, URL коректний?",
|
"app_sources_fetch_failed": "Не вдалося отримати джерельні файли, URL-адреса правильна?",
|
||||||
"app_restore_script_failed": "Сталася помилка всередині скрипта відновити оригінальну програму",
|
"app_restore_script_failed": "Сталася помилка всередині скрипта відновлення застосунку",
|
||||||
"app_restore_failed": "Не вдалося відновити {app}: {error}",
|
"app_restore_failed": "Не вдалося відновити {app}: {error}",
|
||||||
"app_remove_after_failed_install": "Видалення програми після збою установки...",
|
"app_remove_after_failed_install": "Вилучення застосунку після збою встановлення...",
|
||||||
"app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.",
|
"app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.",
|
||||||
"app_requirements_checking": "Перевірка необхідних пакетів для {app}...",
|
"app_requirements_checking": "Перевіряння необхідних пакетів для {app}...",
|
||||||
"app_removed": "{App} видалено",
|
"app_removed": "{app} видалено",
|
||||||
"app_not_properly_removed": "{App} не було видалено належним чином",
|
"app_not_properly_removed": "{app} не було видалено належним чином",
|
||||||
"app_not_installed": "Не вдалося знайти {app} в списку встановлених додатків: {all_apps}",
|
"app_not_installed": "Не вдалося знайти {app} в списку встановлених застосунків: {all_apps}",
|
||||||
"app_not_correctly_installed": "{App}, схоже, неправильно встановлено",
|
"app_not_correctly_installed": "{app}, схоже, неправильно встановлено",
|
||||||
"app_not_upgraded": "Додаток '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких програмах було скасовано: {apps}",
|
"app_not_upgraded": "Застосунок '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких застосунків було скасовано: {apps}",
|
||||||
"app_manifest_install_ask_is_public": "Чи повинно це додаток бути відкрито для анонімних відвідувачів?",
|
"app_manifest_install_ask_is_public": "Чи має цей застосунок бути відкритим для анонімних відвідувачів?",
|
||||||
"app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього додатка",
|
"app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього застосунку",
|
||||||
"app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього додатка"
|
"app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import psutil
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from logging import FileHandler, getLogger, Formatter
|
from logging import FileHandler, getLogger, Formatter
|
||||||
|
from io import IOBase
|
||||||
|
|
||||||
from moulinette import m18n, Moulinette
|
from moulinette import m18n, Moulinette
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
@ -370,6 +371,18 @@ def is_unit_operation(
|
||||||
for field in exclude:
|
for field in exclude:
|
||||||
if field in context:
|
if field in context:
|
||||||
context.pop(field, None)
|
context.pop(field, None)
|
||||||
|
|
||||||
|
# Context is made from args given to main function by argparse
|
||||||
|
# This context will be added in extra parameters in yml file, so this context should
|
||||||
|
# be serializable and short enough (it will be displayed in webadmin)
|
||||||
|
# Argparse can provide some File or Stream, so here we display the filename or
|
||||||
|
# the IOBase, if we have no name.
|
||||||
|
for field, value in context.items():
|
||||||
|
if isinstance(value, IOBase):
|
||||||
|
try:
|
||||||
|
context[field] = value.name
|
||||||
|
except:
|
||||||
|
context[field] = "IOBase"
|
||||||
operation_logger = OperationLogger(op_key, related_to, args=context)
|
operation_logger = OperationLogger(op_key, related_to, args=context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -8,6 +8,10 @@ from yunohost.user import (
|
||||||
user_create,
|
user_create,
|
||||||
user_delete,
|
user_delete,
|
||||||
user_update,
|
user_update,
|
||||||
|
user_import,
|
||||||
|
user_export,
|
||||||
|
FIELDS_FOR_IMPORT,
|
||||||
|
FIRST_ALIASES,
|
||||||
user_group_list,
|
user_group_list,
|
||||||
user_group_create,
|
user_group_create,
|
||||||
user_group_delete,
|
user_group_delete,
|
||||||
|
@ -22,7 +26,7 @@ maindomain = ""
|
||||||
|
|
||||||
def clean_user_groups():
|
def clean_user_groups():
|
||||||
for u in user_list()["users"]:
|
for u in user_list()["users"]:
|
||||||
user_delete(u)
|
user_delete(u, purge=True)
|
||||||
|
|
||||||
for g in user_group_list()["groups"]:
|
for g in user_group_list()["groups"]:
|
||||||
if g not in ["all_users", "visitors"]:
|
if g not in ["all_users", "visitors"]:
|
||||||
|
@ -110,6 +114,77 @@ def test_del_user(mocker):
|
||||||
assert "alice" not in group_res["all_users"]["members"]
|
assert "alice" not in group_res["all_users"]["members"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_user(mocker):
|
||||||
|
import csv
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
fieldnames = [
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"password",
|
||||||
|
"mailbox-quota",
|
||||||
|
"mail",
|
||||||
|
"mail-alias",
|
||||||
|
"mail-forward",
|
||||||
|
"groups",
|
||||||
|
]
|
||||||
|
with StringIO() as csv_io:
|
||||||
|
writer = csv.DictWriter(csv_io, fieldnames, delimiter=";", quotechar='"')
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerow(
|
||||||
|
{
|
||||||
|
"username": "albert",
|
||||||
|
"firstname": "Albert",
|
||||||
|
"lastname": "Good",
|
||||||
|
"password": "",
|
||||||
|
"mailbox-quota": "1G",
|
||||||
|
"mail": "albert@" + maindomain,
|
||||||
|
"mail-alias": "albert2@" + maindomain,
|
||||||
|
"mail-forward": "albert@example.com",
|
||||||
|
"groups": "dev",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
writer.writerow(
|
||||||
|
{
|
||||||
|
"username": "alice",
|
||||||
|
"firstname": "Alice",
|
||||||
|
"lastname": "White",
|
||||||
|
"password": "",
|
||||||
|
"mailbox-quota": "1G",
|
||||||
|
"mail": "alice@" + maindomain,
|
||||||
|
"mail-alias": "alice1@" + maindomain + ",alice2@" + maindomain,
|
||||||
|
"mail-forward": "",
|
||||||
|
"groups": "apps",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
csv_io.seek(0)
|
||||||
|
with message(mocker, "user_import_success"):
|
||||||
|
user_import(csv_io, update=True, delete=True)
|
||||||
|
|
||||||
|
group_res = user_group_list()["groups"]
|
||||||
|
user_res = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"]
|
||||||
|
assert "albert" in user_res
|
||||||
|
assert "alice" in user_res
|
||||||
|
assert "bob" not in user_res
|
||||||
|
assert len(user_res["alice"]["mail-alias"]) == 2
|
||||||
|
assert "albert" in group_res["dev"]["members"]
|
||||||
|
assert "alice" in group_res["apps"]["members"]
|
||||||
|
assert "alice" not in group_res["dev"]["members"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_user(mocker):
|
||||||
|
result = user_export()
|
||||||
|
aliases = ",".join([alias + maindomain for alias in FIRST_ALIASES])
|
||||||
|
should_be = (
|
||||||
|
"username;firstname;lastname;password;mail;mail-alias;mail-forward;mailbox-quota;groups\r\n"
|
||||||
|
f"alice;Alice;White;;alice@{maindomain};{aliases};;0;dev\r\n"
|
||||||
|
f"bob;Bob;Snow;;bob@{maindomain};;;0;apps\r\n"
|
||||||
|
f"jack;Jack;Black;;jack@{maindomain};;;0;"
|
||||||
|
)
|
||||||
|
assert result == should_be
|
||||||
|
|
||||||
|
|
||||||
def test_create_group(mocker):
|
def test_create_group(mocker):
|
||||||
|
|
||||||
with message(mocker, "group_created", group="adminsys"):
|
with message(mocker, "group_created", group="adminsys"):
|
||||||
|
|
|
@ -43,32 +43,71 @@ from yunohost.log import is_unit_operation
|
||||||
|
|
||||||
logger = getActionLogger("yunohost.user")
|
logger = getActionLogger("yunohost.user")
|
||||||
|
|
||||||
|
FIELDS_FOR_IMPORT = {
|
||||||
|
"username": r"^[a-z0-9_]+$",
|
||||||
|
"firstname": r"^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$",
|
||||||
|
"lastname": r"^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$",
|
||||||
|
"password": r"^|(.{3,})$",
|
||||||
|
"mail": r"^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$",
|
||||||
|
"mail-alias": r"^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$",
|
||||||
|
"mail-forward": r"^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$",
|
||||||
|
"mailbox-quota": r"^(\d+[bkMGT])|0|$",
|
||||||
|
"groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$",
|
||||||
|
}
|
||||||
|
|
||||||
|
FIRST_ALIASES = ["root@", "admin@", "webmaster@", "postmaster@", "abuse@"]
|
||||||
|
|
||||||
|
|
||||||
def user_list(fields=None):
|
def user_list(fields=None):
|
||||||
|
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
user_attrs = {
|
ldap_attrs = {
|
||||||
"uid": "username",
|
"username": "uid",
|
||||||
"cn": "fullname",
|
"password": "", # We can't request password in ldap
|
||||||
|
"fullname": "cn",
|
||||||
|
"firstname": "givenName",
|
||||||
|
"lastname": "sn",
|
||||||
"mail": "mail",
|
"mail": "mail",
|
||||||
"maildrop": "mail-forward",
|
"mail-alias": "mail",
|
||||||
"homeDirectory": "home_path",
|
"mail-forward": "maildrop",
|
||||||
"mailuserquota": "mailbox-quota",
|
"mailbox-quota": "mailuserquota",
|
||||||
|
"groups": "memberOf",
|
||||||
|
"shell": "loginShell",
|
||||||
|
"home-path": "homeDirectory",
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs = ["uid"]
|
def display_default(values, _):
|
||||||
|
return values[0] if len(values) == 1 else values
|
||||||
|
|
||||||
|
display = {
|
||||||
|
"password": lambda values, user: "",
|
||||||
|
"mail": lambda values, user: display_default(values[:1], user),
|
||||||
|
"mail-alias": lambda values, _: values[1:],
|
||||||
|
"mail-forward": lambda values, user: [
|
||||||
|
forward for forward in values if forward != user["uid"][0]
|
||||||
|
],
|
||||||
|
"groups": lambda values, user: [
|
||||||
|
group[3:].split(",")[0]
|
||||||
|
for group in values
|
||||||
|
if not group.startswith("cn=all_users,")
|
||||||
|
and not group.startswith("cn=" + user["uid"][0] + ",")
|
||||||
|
],
|
||||||
|
"shell": lambda values, _: len(values) > 0
|
||||||
|
and values[0].strip() == "/bin/false",
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = set(["uid"])
|
||||||
users = {}
|
users = {}
|
||||||
|
|
||||||
if fields:
|
if not fields:
|
||||||
keys = user_attrs.keys()
|
fields = ["username", "fullname", "mail", "mailbox-quota"]
|
||||||
for attr in fields:
|
|
||||||
if attr in keys:
|
for field in fields:
|
||||||
attrs.append(attr)
|
if field in ldap_attrs:
|
||||||
else:
|
attrs.add(ldap_attrs[field])
|
||||||
raise YunohostError("field_invalid", attr)
|
else:
|
||||||
else:
|
raise YunohostError("field_invalid", field)
|
||||||
attrs = ["uid", "cn", "mail", "mailuserquota"]
|
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
result = ldap.search(
|
result = ldap.search(
|
||||||
|
@ -79,12 +118,13 @@ def user_list(fields=None):
|
||||||
|
|
||||||
for user in result:
|
for user in result:
|
||||||
entry = {}
|
entry = {}
|
||||||
for attr, values in user.items():
|
for field in fields:
|
||||||
if values:
|
values = []
|
||||||
entry[user_attrs[attr]] = values[0]
|
if ldap_attrs[field] in user:
|
||||||
|
values = user[ldap_attrs[field]]
|
||||||
|
entry[field] = display.get(field, display_default)(values, user)
|
||||||
|
|
||||||
uid = entry[user_attrs["uid"]]
|
users[user["uid"][0]] = entry
|
||||||
users[uid] = entry
|
|
||||||
|
|
||||||
return {"users": users}
|
return {"users": users}
|
||||||
|
|
||||||
|
@ -99,6 +139,7 @@ def user_create(
|
||||||
password,
|
password,
|
||||||
mailbox_quota="0",
|
mailbox_quota="0",
|
||||||
mail=None,
|
mail=None,
|
||||||
|
from_import=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
from yunohost.domain import domain_list, _get_maindomain
|
from yunohost.domain import domain_list, _get_maindomain
|
||||||
|
@ -156,18 +197,13 @@ def user_create(
|
||||||
raise YunohostValidationError("system_username_exists")
|
raise YunohostValidationError("system_username_exists")
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
aliases = [
|
aliases = [alias + main_domain for alias in FIRST_ALIASES]
|
||||||
"root@" + main_domain,
|
|
||||||
"admin@" + main_domain,
|
|
||||||
"webmaster@" + main_domain,
|
|
||||||
"postmaster@" + main_domain,
|
|
||||||
"abuse@" + main_domain,
|
|
||||||
]
|
|
||||||
|
|
||||||
if mail in aliases:
|
if mail in aliases:
|
||||||
raise YunohostValidationError("mail_unavailable")
|
raise YunohostValidationError("mail_unavailable")
|
||||||
|
|
||||||
operation_logger.start()
|
if not from_import:
|
||||||
|
operation_logger.start()
|
||||||
|
|
||||||
# Get random UID/GID
|
# Get random UID/GID
|
||||||
all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
|
all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
|
||||||
|
@ -221,8 +257,9 @@ def user_create(
|
||||||
# Attempt to create user home folder
|
# Attempt to create user home folder
|
||||||
subprocess.check_call(["mkhomedir_helper", username])
|
subprocess.check_call(["mkhomedir_helper", username])
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
if not os.path.isdir("/home/{0}".format(username)):
|
home = f"/home/{username}"
|
||||||
logger.warning(m18n.n("user_home_creation_failed"), exc_info=1)
|
if not os.path.isdir(home):
|
||||||
|
logger.warning(m18n.n("user_home_creation_failed", home=home), exc_info=1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
|
@ -247,13 +284,14 @@ def user_create(
|
||||||
hook_callback("post_user_create", args=[username, mail], env=env_dict)
|
hook_callback("post_user_create", args=[username, mail], env=env_dict)
|
||||||
|
|
||||||
# TODO: Send a welcome mail to user
|
# TODO: Send a welcome mail to user
|
||||||
logger.success(m18n.n("user_created"))
|
if not from_import:
|
||||||
|
logger.success(m18n.n("user_created"))
|
||||||
|
|
||||||
return {"fullname": fullname, "username": username, "mail": mail}
|
return {"fullname": fullname, "username": username, "mail": mail}
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation([("username", "user")])
|
@is_unit_operation([("username", "user")])
|
||||||
def user_delete(operation_logger, username, purge=False):
|
def user_delete(operation_logger, username, purge=False, from_import=False):
|
||||||
"""
|
"""
|
||||||
Delete user
|
Delete user
|
||||||
|
|
||||||
|
@ -268,7 +306,8 @@ def user_delete(operation_logger, username, purge=False):
|
||||||
if username not in user_list()["users"]:
|
if username not in user_list()["users"]:
|
||||||
raise YunohostValidationError("user_unknown", user=username)
|
raise YunohostValidationError("user_unknown", user=username)
|
||||||
|
|
||||||
operation_logger.start()
|
if not from_import:
|
||||||
|
operation_logger.start()
|
||||||
|
|
||||||
user_group_update("all_users", remove=username, force=True, sync_perm=False)
|
user_group_update("all_users", remove=username, force=True, sync_perm=False)
|
||||||
for group, infos in user_group_list()["groups"].items():
|
for group, infos in user_group_list()["groups"].items():
|
||||||
|
@ -300,7 +339,8 @@ def user_delete(operation_logger, username, purge=False):
|
||||||
|
|
||||||
hook_callback("post_user_delete", args=[username, purge])
|
hook_callback("post_user_delete", args=[username, purge])
|
||||||
|
|
||||||
logger.success(m18n.n("user_deleted"))
|
if not from_import:
|
||||||
|
logger.success(m18n.n("user_deleted"))
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation([("username", "user")], exclude=["change_password"])
|
@is_unit_operation([("username", "user")], exclude=["change_password"])
|
||||||
|
@ -316,6 +356,7 @@ def user_update(
|
||||||
add_mailalias=None,
|
add_mailalias=None,
|
||||||
remove_mailalias=None,
|
remove_mailalias=None,
|
||||||
mailbox_quota=None,
|
mailbox_quota=None,
|
||||||
|
from_import=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Update user informations
|
Update user informations
|
||||||
|
@ -375,7 +416,7 @@ def user_update(
|
||||||
]
|
]
|
||||||
|
|
||||||
# change_password is None if user_update is not called to change the password
|
# change_password is None if user_update is not called to change the password
|
||||||
if change_password is not None:
|
if change_password is not None and change_password != "":
|
||||||
# when in the cli interface if the option to change the password is called
|
# when in the cli interface if the option to change the password is called
|
||||||
# without a specified value, change_password will be set to the const 0.
|
# without a specified value, change_password will be set to the const 0.
|
||||||
# In this case we prompt for the new password.
|
# In this case we prompt for the new password.
|
||||||
|
@ -389,38 +430,40 @@ def user_update(
|
||||||
|
|
||||||
if mail:
|
if mail:
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
aliases = [
|
aliases = [alias + main_domain for alias in FIRST_ALIASES]
|
||||||
"root@" + main_domain,
|
|
||||||
"admin@" + main_domain,
|
# If the requested mail address is already as main address or as an alias by this user
|
||||||
"webmaster@" + main_domain,
|
if mail in user["mail"]:
|
||||||
"postmaster@" + main_domain,
|
user["mail"].remove(mail)
|
||||||
]
|
# Othewise, check that this mail address is not already used by this user
|
||||||
try:
|
else:
|
||||||
ldap.validate_uniqueness({"mail": mail})
|
try:
|
||||||
except Exception as e:
|
ldap.validate_uniqueness({"mail": mail})
|
||||||
raise YunohostValidationError("user_update_failed", user=username, error=e)
|
except Exception as e:
|
||||||
|
raise YunohostError("user_update_failed", user=username, error=e)
|
||||||
if mail[mail.find("@") + 1 :] not in domains:
|
if mail[mail.find("@") + 1 :] not in domains:
|
||||||
raise YunohostValidationError(
|
raise YunohostError(
|
||||||
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
||||||
)
|
)
|
||||||
if mail in aliases:
|
if mail in aliases:
|
||||||
raise YunohostValidationError("mail_unavailable")
|
raise YunohostValidationError("mail_unavailable")
|
||||||
|
|
||||||
del user["mail"][0]
|
new_attr_dict["mail"] = [mail] + user["mail"][1:]
|
||||||
new_attr_dict["mail"] = [mail] + user["mail"]
|
|
||||||
|
|
||||||
if add_mailalias:
|
if add_mailalias:
|
||||||
if not isinstance(add_mailalias, list):
|
if not isinstance(add_mailalias, list):
|
||||||
add_mailalias = [add_mailalias]
|
add_mailalias = [add_mailalias]
|
||||||
for mail in add_mailalias:
|
for mail in add_mailalias:
|
||||||
try:
|
# (c.f. similar stuff as before)
|
||||||
ldap.validate_uniqueness({"mail": mail})
|
if mail in user["mail"]:
|
||||||
except Exception as e:
|
user["mail"].remove(mail)
|
||||||
raise YunohostValidationError(
|
else:
|
||||||
"user_update_failed", user=username, error=e
|
try:
|
||||||
)
|
ldap.validate_uniqueness({"mail": mail})
|
||||||
|
except Exception as e:
|
||||||
|
raise YunohostError("user_update_failed", user=username, error=e)
|
||||||
if mail[mail.find("@") + 1 :] not in domains:
|
if mail[mail.find("@") + 1 :] not in domains:
|
||||||
raise YunohostValidationError(
|
raise YunohostError(
|
||||||
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
||||||
)
|
)
|
||||||
user["mail"].append(mail)
|
user["mail"].append(mail)
|
||||||
|
@ -465,7 +508,8 @@ def user_update(
|
||||||
new_attr_dict["mailuserquota"] = [mailbox_quota]
|
new_attr_dict["mailuserquota"] = [mailbox_quota]
|
||||||
env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota
|
env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota
|
||||||
|
|
||||||
operation_logger.start()
|
if not from_import:
|
||||||
|
operation_logger.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.update("uid=%s,ou=users" % username, new_attr_dict)
|
ldap.update("uid=%s,ou=users" % username, new_attr_dict)
|
||||||
|
@ -475,9 +519,10 @@ def user_update(
|
||||||
# Trigger post_user_update hooks
|
# Trigger post_user_update hooks
|
||||||
hook_callback("post_user_update", env=env_dict)
|
hook_callback("post_user_update", env=env_dict)
|
||||||
|
|
||||||
logger.success(m18n.n("user_updated"))
|
if not from_import:
|
||||||
app_ssowatconf()
|
app_ssowatconf()
|
||||||
return user_info(username)
|
logger.success(m18n.n("user_updated"))
|
||||||
|
return user_info(username)
|
||||||
|
|
||||||
|
|
||||||
def user_info(username):
|
def user_info(username):
|
||||||
|
@ -512,6 +557,8 @@ def user_info(username):
|
||||||
"firstname": user["givenName"][0],
|
"firstname": user["givenName"][0],
|
||||||
"lastname": user["sn"][0],
|
"lastname": user["sn"][0],
|
||||||
"mail": user["mail"][0],
|
"mail": user["mail"][0],
|
||||||
|
"mail-aliases": [],
|
||||||
|
"mail-forward": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(user["mail"]) > 1:
|
if len(user["mail"]) > 1:
|
||||||
|
@ -566,6 +613,315 @@ def user_info(username):
|
||||||
return result_dict
|
return result_dict
|
||||||
|
|
||||||
|
|
||||||
|
def user_export():
|
||||||
|
"""
|
||||||
|
Export users into CSV
|
||||||
|
|
||||||
|
Keyword argument:
|
||||||
|
csv -- CSV file with columns username;firstname;lastname;password;mailbox-quota;mail;mail-alias;mail-forward;groups
|
||||||
|
|
||||||
|
"""
|
||||||
|
import csv # CSV are needed only in this function
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
with StringIO() as csv_io:
|
||||||
|
writer = csv.DictWriter(
|
||||||
|
csv_io, list(FIELDS_FOR_IMPORT.keys()), delimiter=";", quotechar='"'
|
||||||
|
)
|
||||||
|
writer.writeheader()
|
||||||
|
users = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"]
|
||||||
|
for username, user in users.items():
|
||||||
|
user["mail-alias"] = ",".join(user["mail-alias"])
|
||||||
|
user["mail-forward"] = ",".join(user["mail-forward"])
|
||||||
|
user["groups"] = ",".join(user["groups"])
|
||||||
|
writer.writerow(user)
|
||||||
|
|
||||||
|
body = csv_io.getvalue().rstrip()
|
||||||
|
if Moulinette.interface.type == "api":
|
||||||
|
# We return a raw bottle HTTPresponse (instead of serializable data like
|
||||||
|
# list/dict, ...), which is gonna be picked and used directly by moulinette
|
||||||
|
from bottle import HTTPResponse
|
||||||
|
|
||||||
|
response = HTTPResponse(
|
||||||
|
body=body,
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": "attachment; filename=users.csv",
|
||||||
|
"Content-Type": "text/csv",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
@is_unit_operation()
|
||||||
|
def user_import(operation_logger, csvfile, update=False, delete=False):
|
||||||
|
"""
|
||||||
|
Import users from CSV
|
||||||
|
|
||||||
|
Keyword argument:
|
||||||
|
csvfile -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv # CSV are needed only in this function
|
||||||
|
from moulinette.utils.text import random_ascii
|
||||||
|
from yunohost.permission import permission_sync_to_user
|
||||||
|
from yunohost.app import app_ssowatconf
|
||||||
|
from yunohost.domain import domain_list
|
||||||
|
|
||||||
|
# Pre-validate data and prepare what should be done
|
||||||
|
actions = {"created": [], "updated": [], "deleted": []}
|
||||||
|
is_well_formatted = True
|
||||||
|
|
||||||
|
def to_list(str_list):
|
||||||
|
L = str_list.split(",") if str_list else []
|
||||||
|
L = [l.strip() for l in L]
|
||||||
|
return L
|
||||||
|
|
||||||
|
existing_users = user_list()["users"]
|
||||||
|
existing_groups = user_group_list()["groups"]
|
||||||
|
existing_domains = domain_list()["domains"]
|
||||||
|
|
||||||
|
reader = csv.DictReader(csvfile, delimiter=";", quotechar='"')
|
||||||
|
users_in_csv = []
|
||||||
|
|
||||||
|
missing_columns = [
|
||||||
|
key for key in FIELDS_FOR_IMPORT.keys() if key not in reader.fieldnames
|
||||||
|
]
|
||||||
|
if missing_columns:
|
||||||
|
raise YunohostValidationError(
|
||||||
|
"user_import_missing_columns", columns=", ".join(missing_columns)
|
||||||
|
)
|
||||||
|
|
||||||
|
for user in reader:
|
||||||
|
|
||||||
|
# Validate column values against regexes
|
||||||
|
format_errors = [
|
||||||
|
f"{key}: '{user[key]}' doesn't match the expected format"
|
||||||
|
for key, validator in FIELDS_FOR_IMPORT.items()
|
||||||
|
if user[key] is None or not re.match(validator, user[key])
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check for duplicated username lines
|
||||||
|
if user["username"] in users_in_csv:
|
||||||
|
format_errors.append(f"username '{user['username']}' duplicated")
|
||||||
|
users_in_csv.append(user["username"])
|
||||||
|
|
||||||
|
# Validate that groups exist
|
||||||
|
user["groups"] = to_list(user["groups"])
|
||||||
|
unknown_groups = [g for g in user["groups"] if g not in existing_groups]
|
||||||
|
if unknown_groups:
|
||||||
|
format_errors.append(
|
||||||
|
f"username '{user['username']}': unknown groups %s"
|
||||||
|
% ", ".join(unknown_groups)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate that domains exist
|
||||||
|
user["mail-alias"] = to_list(user["mail-alias"])
|
||||||
|
user["mail-forward"] = to_list(user["mail-forward"])
|
||||||
|
user["domain"] = user["mail"].split("@")[1]
|
||||||
|
|
||||||
|
unknown_domains = []
|
||||||
|
if user["domain"] not in existing_domains:
|
||||||
|
unknown_domains.append(user["domain"])
|
||||||
|
|
||||||
|
unknown_domains += [
|
||||||
|
mail.split("@", 1)[1]
|
||||||
|
for mail in user["mail-alias"]
|
||||||
|
if mail.split("@", 1)[1] not in existing_domains
|
||||||
|
]
|
||||||
|
unknown_domains = set(unknown_domains)
|
||||||
|
|
||||||
|
if unknown_domains:
|
||||||
|
format_errors.append(
|
||||||
|
f"username '{user['username']}': unknown domains %s"
|
||||||
|
% ", ".join(unknown_domains)
|
||||||
|
)
|
||||||
|
|
||||||
|
if format_errors:
|
||||||
|
logger.error(
|
||||||
|
m18n.n(
|
||||||
|
"user_import_bad_line",
|
||||||
|
line=reader.line_num,
|
||||||
|
details=", ".join(format_errors),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
is_well_formatted = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Choose what to do with this line and prepare data
|
||||||
|
user["mailbox-quota"] = user["mailbox-quota"] or "0"
|
||||||
|
|
||||||
|
# User creation
|
||||||
|
if user["username"] not in existing_users:
|
||||||
|
# Generate password if not exists
|
||||||
|
# This could be used when reset password will be merged
|
||||||
|
if not user["password"]:
|
||||||
|
user["password"] = random_ascii(70)
|
||||||
|
actions["created"].append(user)
|
||||||
|
# User update
|
||||||
|
elif update:
|
||||||
|
actions["updated"].append(user)
|
||||||
|
|
||||||
|
if delete:
|
||||||
|
actions["deleted"] = [
|
||||||
|
user for user in existing_users if user not in users_in_csv
|
||||||
|
]
|
||||||
|
|
||||||
|
if delete and not users_in_csv:
|
||||||
|
logger.error(
|
||||||
|
"You used the delete option with an empty csv file ... You probably did not really mean to do that, did you !?"
|
||||||
|
)
|
||||||
|
is_well_formatted = False
|
||||||
|
|
||||||
|
if not is_well_formatted:
|
||||||
|
raise YunohostValidationError("user_import_bad_file")
|
||||||
|
|
||||||
|
total = len(actions["created"] + actions["updated"] + actions["deleted"])
|
||||||
|
|
||||||
|
if total == 0:
|
||||||
|
logger.info(m18n.n("user_import_nothing_to_do"))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply creation, update and deletion operation
|
||||||
|
result = {"created": 0, "updated": 0, "deleted": 0, "errors": 0}
|
||||||
|
|
||||||
|
def progress(info=""):
|
||||||
|
progress.nb += 1
|
||||||
|
width = 20
|
||||||
|
bar = int(progress.nb * width / total)
|
||||||
|
bar = "[" + "#" * bar + "." * (width - bar) + "]"
|
||||||
|
if info:
|
||||||
|
bar += " > " + info
|
||||||
|
if progress.old == bar:
|
||||||
|
return
|
||||||
|
progress.old = bar
|
||||||
|
logger.info(bar)
|
||||||
|
|
||||||
|
progress.nb = 0
|
||||||
|
progress.old = ""
|
||||||
|
|
||||||
|
def on_failure(user, exception):
|
||||||
|
result["errors"] += 1
|
||||||
|
logger.error(user + ": " + str(exception))
|
||||||
|
|
||||||
|
def update(new_infos, old_infos=False):
|
||||||
|
remove_alias = None
|
||||||
|
remove_forward = None
|
||||||
|
remove_groups = []
|
||||||
|
add_groups = new_infos["groups"]
|
||||||
|
if old_infos:
|
||||||
|
new_infos["mail"] = (
|
||||||
|
None if old_infos["mail"] == new_infos["mail"] else new_infos["mail"]
|
||||||
|
)
|
||||||
|
remove_alias = list(
|
||||||
|
set(old_infos["mail-alias"]) - set(new_infos["mail-alias"])
|
||||||
|
)
|
||||||
|
remove_forward = list(
|
||||||
|
set(old_infos["mail-forward"]) - set(new_infos["mail-forward"])
|
||||||
|
)
|
||||||
|
new_infos["mail-alias"] = list(
|
||||||
|
set(new_infos["mail-alias"]) - set(old_infos["mail-alias"])
|
||||||
|
)
|
||||||
|
new_infos["mail-forward"] = list(
|
||||||
|
set(new_infos["mail-forward"]) - set(old_infos["mail-forward"])
|
||||||
|
)
|
||||||
|
|
||||||
|
remove_groups = list(set(old_infos["groups"]) - set(new_infos["groups"]))
|
||||||
|
add_groups = list(set(new_infos["groups"]) - set(old_infos["groups"]))
|
||||||
|
|
||||||
|
for group, infos in existing_groups.items():
|
||||||
|
# Loop only on groups in 'remove_groups'
|
||||||
|
# Ignore 'all_users' and primary group
|
||||||
|
if (
|
||||||
|
group in ["all_users", new_infos["username"]]
|
||||||
|
or group not in remove_groups
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
# If the user is in this group (and it's not the primary group),
|
||||||
|
# remove the member from the group
|
||||||
|
if new_infos["username"] in infos["members"]:
|
||||||
|
user_group_update(
|
||||||
|
group,
|
||||||
|
remove=new_infos["username"],
|
||||||
|
sync_perm=False,
|
||||||
|
from_import=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_update(
|
||||||
|
new_infos["username"],
|
||||||
|
new_infos["firstname"],
|
||||||
|
new_infos["lastname"],
|
||||||
|
new_infos["mail"],
|
||||||
|
new_infos["password"],
|
||||||
|
mailbox_quota=new_infos["mailbox-quota"],
|
||||||
|
mail=new_infos["mail"],
|
||||||
|
add_mailalias=new_infos["mail-alias"],
|
||||||
|
remove_mailalias=remove_alias,
|
||||||
|
remove_mailforward=remove_forward,
|
||||||
|
add_mailforward=new_infos["mail-forward"],
|
||||||
|
from_import=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for group in add_groups:
|
||||||
|
if group in ["all_users", new_infos["username"]]:
|
||||||
|
continue
|
||||||
|
user_group_update(
|
||||||
|
group, add=new_infos["username"], sync_perm=False, from_import=True
|
||||||
|
)
|
||||||
|
|
||||||
|
users = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"]
|
||||||
|
operation_logger.start()
|
||||||
|
# We do delete and update before to avoid mail uniqueness issues
|
||||||
|
for user in actions["deleted"]:
|
||||||
|
try:
|
||||||
|
user_delete(user, purge=True, from_import=True)
|
||||||
|
result["deleted"] += 1
|
||||||
|
except YunohostError as e:
|
||||||
|
on_failure(user, e)
|
||||||
|
progress(f"Deleting {user}")
|
||||||
|
|
||||||
|
for user in actions["updated"]:
|
||||||
|
try:
|
||||||
|
update(user, users[user["username"]])
|
||||||
|
result["updated"] += 1
|
||||||
|
except YunohostError as e:
|
||||||
|
on_failure(user["username"], e)
|
||||||
|
progress(f"Updating {user['username']}")
|
||||||
|
|
||||||
|
for user in actions["created"]:
|
||||||
|
try:
|
||||||
|
user_create(
|
||||||
|
user["username"],
|
||||||
|
user["firstname"],
|
||||||
|
user["lastname"],
|
||||||
|
user["domain"],
|
||||||
|
user["password"],
|
||||||
|
user["mailbox-quota"],
|
||||||
|
from_import=True,
|
||||||
|
)
|
||||||
|
update(user)
|
||||||
|
result["created"] += 1
|
||||||
|
except YunohostError as e:
|
||||||
|
on_failure(user["username"], e)
|
||||||
|
progress(f"Creating {user['username']}")
|
||||||
|
|
||||||
|
permission_sync_to_user()
|
||||||
|
app_ssowatconf()
|
||||||
|
|
||||||
|
if result["errors"]:
|
||||||
|
msg = m18n.n("user_import_partial_failed")
|
||||||
|
if result["created"] + result["updated"] + result["deleted"] == 0:
|
||||||
|
msg = m18n.n("user_import_failed")
|
||||||
|
logger.error(msg)
|
||||||
|
operation_logger.error(msg)
|
||||||
|
else:
|
||||||
|
logger.success(m18n.n("user_import_success"))
|
||||||
|
operation_logger.success()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Group subcategory
|
# Group subcategory
|
||||||
#
|
#
|
||||||
|
@ -740,7 +1096,13 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
|
||||||
|
|
||||||
@is_unit_operation([("groupname", "group")])
|
@is_unit_operation([("groupname", "group")])
|
||||||
def user_group_update(
|
def user_group_update(
|
||||||
operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True
|
operation_logger,
|
||||||
|
groupname,
|
||||||
|
add=None,
|
||||||
|
remove=None,
|
||||||
|
force=False,
|
||||||
|
sync_perm=True,
|
||||||
|
from_import=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Update user informations
|
Update user informations
|
||||||
|
@ -810,7 +1172,8 @@ def user_group_update(
|
||||||
]
|
]
|
||||||
|
|
||||||
if set(new_group) != set(current_group):
|
if set(new_group) != set(current_group):
|
||||||
operation_logger.start()
|
if not from_import:
|
||||||
|
operation_logger.start()
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
try:
|
try:
|
||||||
ldap.update(
|
ldap.update(
|
||||||
|
@ -820,14 +1183,16 @@ def user_group_update(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("group_update_failed", group=groupname, error=e)
|
raise YunohostError("group_update_failed", group=groupname, error=e)
|
||||||
|
|
||||||
if groupname != "all_users":
|
|
||||||
logger.success(m18n.n("group_updated", group=groupname))
|
|
||||||
else:
|
|
||||||
logger.debug(m18n.n("group_updated", group=groupname))
|
|
||||||
|
|
||||||
if sync_perm:
|
if sync_perm:
|
||||||
permission_sync_to_user()
|
permission_sync_to_user()
|
||||||
return user_group_info(groupname)
|
|
||||||
|
if not from_import:
|
||||||
|
if groupname != "all_users":
|
||||||
|
logger.success(m18n.n("group_updated", group=groupname))
|
||||||
|
else:
|
||||||
|
logger.debug(m18n.n("group_updated", group=groupname))
|
||||||
|
|
||||||
|
return user_group_info(groupname)
|
||||||
|
|
||||||
|
|
||||||
def user_group_info(groupname):
|
def user_group_info(groupname):
|
||||||
|
|
47
tests/autofix_locale_format.py
Normal file
47
tests/autofix_locale_format.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import glob
|
||||||
|
|
||||||
|
# List all locale files (except en.json being the ref)
|
||||||
|
locale_folder = "locales/"
|
||||||
|
locale_files = glob.glob(locale_folder + "*.json")
|
||||||
|
locale_files = [filename.split("/")[-1] for filename in locale_files]
|
||||||
|
locale_files.remove("en.json")
|
||||||
|
|
||||||
|
reference = json.loads(open(locale_folder + "en.json").read())
|
||||||
|
|
||||||
|
|
||||||
|
def fix_locale(locale_file):
|
||||||
|
|
||||||
|
this_locale = json.loads(open(locale_folder + locale_file).read())
|
||||||
|
fixed_stuff = False
|
||||||
|
|
||||||
|
# We iterate over all keys/string in en.json
|
||||||
|
for key, string in reference.items():
|
||||||
|
|
||||||
|
# Ignore check if there's no translation yet for this key
|
||||||
|
if key not in this_locale:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Then we check that every "{stuff}" (for python's .format())
|
||||||
|
# should also be in the translated string, otherwise the .format
|
||||||
|
# will trigger an exception!
|
||||||
|
subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)]
|
||||||
|
subkeys_in_this_locale = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])]
|
||||||
|
|
||||||
|
if set(subkeys_in_ref) != set(subkeys_in_this_locale) and (len(subkeys_in_ref) == len(subkeys_in_this_locale)):
|
||||||
|
for i, subkey in enumerate(subkeys_in_ref):
|
||||||
|
this_locale[key] = this_locale[key].replace('{%s}' % subkeys_in_this_locale[i], '{%s}' % subkey)
|
||||||
|
fixed_stuff = True
|
||||||
|
|
||||||
|
if fixed_stuff:
|
||||||
|
json.dump(
|
||||||
|
this_locale,
|
||||||
|
open(locale_folder + locale_file, "w"),
|
||||||
|
indent=4,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
for locale_file in locale_files:
|
||||||
|
fix_locale(locale_file)
|
Loading…
Add table
Reference in a new issue