diff --git a/l10n/de_DE.js b/l10n/de_DE.js new file mode 100644 index 0000000000000000000000000000000000000000..f1195cc646ad493fb7ced35963c765159f808938 --- /dev/null +++ b/l10n/de_DE.js @@ -0,0 +1,10 @@ +OC.L10N.register( + "email-recovery", + { + "Email Recovery" : "E-Mail-Wiederherstellung", + "Email Recovery App" : "E-Mail-Wiederherstellungsapp", + "Recovery Email" : "Wiederherstellungs-E-Mail", + "Recovery Email " : "Wiederherstellungs-E-Mail ", + "Changes saved" : "Änderungen gespeichert" +}, +"nplurals=2; plural=n != 1;"); diff --git a/l10n/de_DE.json b/l10n/de_DE.json new file mode 100644 index 0000000000000000000000000000000000000000..867c7cbb12138d71c28e10599420cfede435285d --- /dev/null +++ b/l10n/de_DE.json @@ -0,0 +1,8 @@ +{ "translations": { + "Email Recovery" : "E-Mail-Wiederherstellung", + "Email Recovery App" : "E-Mail-Wiederherstellungsapp", + "Recovery Email" : "Wiederherstellungs-E-Mail", + "Recovery Email " : "Wiederherstellungs-E-Mail ", + "Changes saved" : "Änderungen gespeichert" +},"pluralForm" :"nplurals=2; plural=n != 1;" +} \ No newline at end of file diff --git a/l10n/es.js b/l10n/es.js new file mode 100644 index 0000000000000000000000000000000000000000..27d58da6d1782849c2b56f28307542cbed203138 --- /dev/null +++ b/l10n/es.js @@ -0,0 +1,10 @@ +OC.L10N.register( + "email-recovery", + { + "Email Recovery" : "Recuperación del correo electrónico", + "Email Recovery App" : "Aplicación de recuperación de correo electrónico", + "Recovery Email" : "Correo electrónico de recuperación", + "Recovery Email " : "Correo electrónico de recuperación ", + "Changes saved" : "Cambios guardados" +}, +"nplurals=2; plural=n != 1;"); diff --git a/l10n/es.json b/l10n/es.json new file mode 100644 index 0000000000000000000000000000000000000000..b32e59aabda15bae80495d6404b5cc08418751ac --- /dev/null +++ b/l10n/es.json @@ -0,0 +1,8 @@ +{ "translations": { + "Email Recovery" : "Recuperación del correo electrónico", + "Email Recovery App" : "Aplicación de recuperación de correo electrónico", + "Recovery Email" : "Correo electrónico de recuperación", + "Recovery Email " : "Correo electrónico de recuperación ", + "Changes saved" : "Cambios guardados" +},"pluralForm" :"nplurals=2; plural=n != 1;" +} \ No newline at end of file diff --git a/l10n/fr.js b/l10n/fr.js new file mode 100644 index 0000000000000000000000000000000000000000..10610559305a77f8fc74f95896033231564817ff --- /dev/null +++ b/l10n/fr.js @@ -0,0 +1,10 @@ +OC.L10N.register( + "email-recovery", + { + "Email Recovery" : "Récupération des e-mails", + "Email Recovery App" : "Application de récupération des e-mails", + "Recovery Email" : "Courriel de récupération", + "Recovery Email " : "Courriel de récupération ", + "Changes saved" : "Changements enregistrés" +}, +"nplurals=2; plural=n > 1;"); diff --git a/l10n/fr.json b/l10n/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..dcdb982b0f8d2b55a47468d101b86ec82516b17b --- /dev/null +++ b/l10n/fr.json @@ -0,0 +1,8 @@ +{ "translations": { + "Email Recovery" : "Récupération des e-mails", + "Email Recovery App" : "Application de récupération des e-mails", + "Recovery Email" : "Courriel de récupération", + "Recovery Email " : "Courriel de récupération ", + "Changes saved" : "Changements enregistrés" +},"pluralForm" :"nplurals=2; plural=n > 1;" +} \ No newline at end of file diff --git a/l10n/it.js b/l10n/it.js new file mode 100644 index 0000000000000000000000000000000000000000..01faa9dcd23f771c947467e4f4963a951eedd472 --- /dev/null +++ b/l10n/it.js @@ -0,0 +1,10 @@ +OC.L10N.register( + "email-recovery", + { + "Email Recovery" : "Recupero delle e-mail", + "Email Recovery App" : "App per il recupero delle e-mail", + "Recovery Email" : "Recupero e-mail", + "Recovery Email " : "Recupero e-mail ", + "Changes saved" : "Modifiche salvate" +}, +"nplurals=2; plural=n != 1;"); diff --git a/l10n/it.json b/l10n/it.json new file mode 100644 index 0000000000000000000000000000000000000000..c19525912189552dc13298c821a4aa6e81846e55 --- /dev/null +++ b/l10n/it.json @@ -0,0 +1,8 @@ +{ "translations": { + "Email Recovery" : "Recupero delle e-mail", + "Email Recovery App" : "App per il recupero delle e-mail", + "Recovery Email" : "Recupero e-mail", + "Recovery Email " : "Recupero e-mail ", + "Changes saved" : "Modifiche salvate" +},"pluralForm" :"nplurals=2; plural=n != 1;" +} \ No newline at end of file diff --git a/lib/Exception/BlacklistedEmailException.php b/lib/Exception/BlacklistedEmailException.php new file mode 100644 index 0000000000000000000000000000000000000000..7138dcbcd12aa5c18d15e958e0e24db53ab09eae --- /dev/null +++ b/lib/Exception/BlacklistedEmailException.php @@ -0,0 +1,9 @@ +util = $util; + $this->mailboxMapper = $mailboxMapper; + $this->logger = $logger; + $this->blackListService = $blackListService; + } + + public function handle(Event $event): void { + if (!($event instanceof UserChangedEvent)) { + return; + } + + $feature = $event->getFeature(); + $user = $event->getUser(); + $username = $user->getUID(); + $newValue = $event->getValue(); + + if ($feature === self::QUOTA_FEATURE) { + $updatedQuota = $event->getValue(); + $quotaInBytes = (int) $this->util->computerFileSize($updatedQuota); + $backend = $user->getBackend()->getBackendName(); + + $this->updateQuota($username, $backend, $quotaInBytes); + } + + if ($feature === self::RECOVERY_EMAIL_FEATURE) { + $recoveryEmail = $event->getValue(); + $recoveryEmailAttribute = [ + 'recoveryMailAddress' => $recoveryEmail + ]; + + $this->blackListService->updateAttributesInLDAP($username, $recoveryEmailAttribute); + } + + if ($feature === self::ENABLED_FEATURE) { + try { + $this->blackListService->mapActiveAttributesInLDAP($username, $newValue); + } catch (Exception $e) { + $this->logger->logException('Failed to update LDAP attributes for user: ' . $username, ['exception' => $e]); + } + } + } + + private function updateQuota(string $username, string $backend, int $quotaInBytes) { + try { + if ($backend === 'SQL raw') { + $this->mailboxMapper->updateMailboxQuota($username, $quotaInBytes); + } + if ($backend === 'LDAP') { + $quotaAttribute = [ + 'quota' => $quotaInBytes + ]; + $this->blackListService->updateAttributesInLDAP($username, $quotaAttribute); + } + } catch (Exception $e) { + $this->logger->error("Error setting quota for user $username " . $e->getMessage()); + } + } +} diff --git a/lib/Service/BlackListService.php b/lib/Service/BlackListService.php new file mode 100644 index 0000000000000000000000000000000000000000..97a07e58566bc315e86fed58a83bb12a99d4ccab --- /dev/null +++ b/lib/Service/BlackListService.php @@ -0,0 +1,168 @@ +logger = $logger; + $this->l10nFactory = $l10nFactory; + $this->LDAPConnectionService = $LDAPConnectionService; + $this->appData = $appData; + $this->appName = $appName; + } + + public function mapActiveAttributesInLDAP(string $username, bool $isEnabled): void + { + $userActiveAttributes = $this->getActiveAttributes($isEnabled); + $this->updateAttributesInLDAP($username, $userActiveAttributes); + } + + private function getActiveAttributes(bool $isEnabled): array + { + return [ + 'active' => $isEnabled ? 'TRUE' : 'FALSE', + 'mailActive' => $isEnabled ? 'TRUE' : 'FALSE', + ]; + } + + public function updateAttributesInLDAP(string $username, array $attributes): void + { + if (!$this->LDAPConnectionService->isLDAPEnabled()) { + return; + } + + $conn = $this->LDAPConnectionService->getLDAPConnection(); + $userDn = $this->LDAPConnectionService->username2dn($username); + + if ($userDn === false) { + throw new Exception('Could not find DN for username: ' . $username); + } + + if (!ldap_modify($conn, $userDn, $attributes)) { + throw new Exception('Could not modify user ' . $username . ' entry at LDAP server. Attributes: ' . print_r($attributes, true)); + } + + $this->LDAPConnectionService->closeLDAPConnection($conn); + } + /** + * Check if an email domain is blacklisted against a JSON list of disposable email domains. + * + * @param string $email The email address to check. + * @return bool True if the email domain is blacklisted, false otherwise. + */ + public function isBlacklistedEmail(string $email): bool + { + if (!$this->ensureDocumentsFolder()) { + return false; + } + $blacklistedDomains = $this->getBlacklistedDomainData(); + if (empty($blacklistedDomains)) { + return false; + } + $emailParts = explode('@', $email); + $emailDomain = strtolower(end($emailParts)); + return in_array($emailDomain, $blacklistedDomains); + } + /** + * Update the blacklisted domains data by fetching it from a URL and saving it locally. + * + * @return void + */ + public function updateBlacklistedDomains(): void + { + $blacklisted_domain_url = self::BLACKLISTED_DOMAINS_URL; + $json_data = file_get_contents($blacklisted_domain_url); + $this->setBlacklistedDomainsData($json_data); + } + /** + * Store blacklisted domain data in a file within AppData. + * + * @param string $data The data to be stored in the file. + * @return void + */ + private function setBlacklistedDomainsData(string $data): void + { + $file = $this->getBlacklistedDomainsFile(); + $file->putContent($data); + } + /** + * Retrieve the blacklisted domain file path + * + * @return ISimpleFile + */ + private function getBlacklistedDomainsFile(): ISimpleFile + { + try { + $currentFolder = $this->appData->getFolder('/'); + } catch (NotFoundException $e) { + $currentFolder = $this->appData->newFolder('/'); + } + $filename = self::BLACKLISTED_DOMAINS_FILE_NAME; + if ($currentFolder->fileExists($filename)) { + return $currentFolder->getFile($filename); + } + return $currentFolder->newFile($filename); + } + /** + * Retrieve the blacklisted domain data. + * + * @return array The array of blacklisted domains. + */ + public function getBlacklistedDomainData(): array + { + $document = self::BLACKLISTED_DOMAINS_FILE_NAME; + $file = $this->getBlacklistedDomainsFile(); + try { + $blacklistedDomainsInJson = $file->getContent(); + if (empty($blacklistedDomainsInJson)) { + return []; + } + return json_decode($blacklistedDomainsInJson, true, 512, JSON_THROW_ON_ERROR); + } catch (NotFoundException $e) { + $this->logger->warning('Blacklisted domains file ' . $document . ' not found!'); + return []; + } catch (\Throwable $e) { + $this->logger->warning('Error decoding blacklisted domains file ' . $document . ': ' . $e->getMessage()); + return []; + } + } + + /** + * Ensure the specified folder exists within AppData. + * + * @return bool + */ + private function ensureDocumentsFolder(): bool + { + try { + $this->appData->getFolder('/'); + } catch (NotFoundException $e) { + $this->logger->error($this->appName . ' AppData folder not found!'); + return false; + } catch (\RuntimeException $e) { + $this->logger->error($this->appName . ' AppData folder not found! Runtime Error: ' . $e->getMessage()); + return false; + } + return true; + } +} diff --git a/lib/Service/LDAPConnectionService.php b/lib/Service/LDAPConnectionService.php new file mode 100644 index 0000000000000000000000000000000000000000..58c3be600e28c257ef5eab072f31138088558c3b --- /dev/null +++ b/lib/Service/LDAPConnectionService.php @@ -0,0 +1,107 @@ +userManager = $userManager; + $this->getConfigurationFromBackend(); + $ldapConfigPrefixes = $ldapBackendHelper->getServerConfigurationPrefixes(true); + $prefix = array_shift($ldapConfigPrefixes); + $this->ldapConfig = new Configuration($prefix); + $this->config = $config; + } + + + private function getConfigurationFromBackend() { + // We don't actually need user id to get access from backend + $uid = ''; + $backends = $this->userManager->getBackends(); + foreach ($backends as $backend) { + if ($backend->getBackendName() === 'LDAP') { + $this->access = $backend->getLDAPAccess($uid); + $connection = $this->access->getConnection(); + $configuration = $connection->getConfiguration(); + + if ($configuration['ldap_configuration_active']) { + $this->ldapEnabled = true; + $this->configuration = $configuration; + break; + } + } + } + } + + public function isUserOnLDAPBackend($user) { + $backend = $user->getBackend(); + return $backend->getBackendName() === 'LDAP'; + } + + public function isLDAPEnabled(): bool { + return $this->ldapEnabled; + } + + public function username2dn(string $username) { + return $this->access->username2dn($username); + } + public function getLDAPConnection() { + if (!$this->ldapEnabled) { + throw new Exception('LDAP backend is not enabled'); + } + + $adminDn = $this->configuration['ldap_dn']; + $adminPassword = $this->configuration['ldap_agent_password']; + $host = $this->configuration['ldap_host']; + $port = intval($this->configuration['ldap_port']); + + $conn = ldap_connect($host, $port); + ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_bind($conn, $adminDn, $adminPassword); + + if (!$conn) { + throw new Exception('Could not connect to LDAP server!'); + } + return $conn; + } + + public function closeLDAPConnection($conn): void { + ldap_close($conn); + } + + public function getLDAPAccess() { + if (!$this->access) { + throw new Exception('Access not defined!'); + } + return $this->access; + } + + public function getLDAPBaseUsers(): array { + $bases = $this->ldapConfig->ldapBaseUsers; + if (empty($bases)) { + $bases = $this->ldapConfig->ldapBase; + } + return $bases; + } + public function getDisplayNameAttribute(): string { + return $this->ldapConfig->ldapUserDisplayName; + } + public function getLdapQuota() { + return $this->config->getSystemValue('default_quota', '1024'); + } +} diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index dcdcf847824d157eb4b4f80dd03e43573aacdb0e..3b5f13a65d2c8b54e935116422a1ff33627fda8b 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -10,7 +10,10 @@ use OCP\IConfig; use OCP\IUserManager; use OCA\EmailRecovery\Exception\InvalidRecoveryEmailException; use OCA\EmailRecovery\Exception\SameRecoveryEmailAsEmailException; -use OCA\EcloudAccounts\Service\LDAPConnectionService; +use OCA\EmailRecovery\Service\LDAPConnectionService; +use OCA\EmailRecovery\Service\BlackListService; +use OCA\EmailRecovery\Exception\BlacklistedEmailException; +use OCA\EmailRecovery\Exception\RecoveryEmailValidationException; class RecoveryEmailService { @@ -18,21 +21,34 @@ class RecoveryEmailService private $config; private $appName; private $userManager; + private $blackListService; + private $logger; - public function __construct(string $appName, ILogger $logger, IConfig $config, LDAPConnectionService $LDAPConnectionService, IUserManager $userManager) + public function __construct(string $appName, ILogger $logger, IConfig $config, LDAPConnectionService $LDAPConnectionService, IUserManager $userManager, BlackListService $blackListService) { $this->logger = $logger; $this->config = $config; $this->appName = $appName; $this->LDAPConnectionService = $LDAPConnectionService; $this->userManager = $userManager; + $this->blackListService = $blackListService; } - public function getRecoveryEmail(string $username) : string { return $this->config->getUserValue($username, $this->appName, 'recovery-email', ''); } - + public function setRecoveryEmail(string $uid, string $recoveryEmail): void { + $this->config->setUserValue($uid, $this->appName, 'recovery-email', $recoveryEmail); + } + public function setUnverifiedRecoveryEmail(string $uid, string $recoveryEmail): void { + $this->config->setUserValue($uid, $this->appName, 'unverified-recovery-email', $recoveryEmail); + } + public function getMainDomain() : string { + return $this->config->getSystemValue('main_domain', ''); + } + public function getLegacyDomain() : string { + return $this->config->getSystemValue('legacy_domain', ''); + } public function validateRecoveryEmail(string $username, string $recoveryEmail) : bool { $user = $this->userManager->get($username); @@ -70,5 +86,79 @@ class RecoveryEmailService throw new Exception('Could not modify user entry'); } $this->LDAPConnectionService->closeLDAPConnection($conn); - } + }/** + * Validates the recovery email address. + * + * @param string $recoveryEmail The recovery email address to be validated. + * @throws Exception If the recovery email address has an incorrect format, is already taken, or if the domain is disallowed. + * @return void + */ + public function validateRecoveryEmailAddress(string $recoveryEmail): void { + if (!$this->isValidEmailFormat($recoveryEmail)) { + throw new RecoveryEmailValidationException('Recovery email address has an incorrect format.'); + } + if ($this->checkRecoveryEmailAvailable($recoveryEmail)) { + throw new RecoveryEmailValidationException('Recovery email address is already taken.'); + } + if ($this->isRecoveryEmailDomainDisallowed($recoveryEmail)) { + throw new RecoveryEmailValidationException('You cannot set an email address with a Murena domain as recovery email address.'); + } + if ($this->blackListService->isBlacklistedEmail($recoveryEmail)) { + throw new BlacklistedEmailException('The domain of this email address is blacklisted. Please provide another recovery address.'); + } + } + /** + * Check if a recovery email address is available (not already taken by another user). + * + * @param string $recoveryEmail The recovery email address to check. + * + * @return bool True if the recovery email address is available, false otherwise. + */ + public function checkRecoveryEmailAvailable(string $recoveryEmail): bool { + $recoveryEmail = strtolower($recoveryEmail); + $users = $this->config->getUsersForUserValue('email-recovery', 'recovery-email', $recoveryEmail); + if(count($users)) { + return true; + } + $users = $this->config->getUsersForUserValue('email-recovery', 'unverified-recovery-email', $recoveryEmail); + if(count($users)) { + return true; + } + return false; + } + + /** + * Check if a recovery email address domain is restricted for some domains + * + * @param string $recoveryEmail The recovery email address to check. + * + * @return bool True if the recovery email address is disallowed, false otherwise. + */ + public function isRecoveryEmailDomainDisallowed(string $recoveryEmail): bool { + + $recoveryEmail = strtolower($recoveryEmail); + + $emailParts = explode('@', $recoveryEmail); + $domain = $emailParts[1] ?? ''; + + $legacyDomain = $this->getLegacyDomain(); + $mainDomain = $this->getMainDomain(); + + $restrictedDomains = [ $legacyDomain, $mainDomain ]; + + return in_array($domain, $restrictedDomains); + } + + /** + * Check if a recovery email address is in valid format + * + * @param string $recoveryEmail The recovery email address to check. + * + * @return bool True if the recovery email address is valid, false otherwise. + */ + public function isValidEmailFormat(string $recoveryEmail): bool { + return filter_var($recoveryEmail, FILTER_VALIDATE_EMAIL) !== false; + } + + } diff --git a/translationfiles/de_DE@informal/email-recovery.po b/translationfiles/de_DE@informal/email-recovery.po new file mode 100644 index 0000000000000000000000000000000000000000..6a539a42aab0ac18bd506ca5cacc50a033d1335b --- /dev/null +++ b/translationfiles/de_DE@informal/email-recovery.po @@ -0,0 +1,44 @@ +# BASE TRANSLATION FILE. +# Copyright (C) 2022 MURENA +# This file is distributed under the same license as the Nextcloud package. +# dev@murena.io +# +msgid "" +msgstr "" +"Project-Id-Version: Nextcloud 3.14159\n" +"Report-Msgid-Bugs-To: translations\\@example.com\n" +"POT-Creation-Date: 2022-05-06 19:52+0530\n" +"PO-Revision-Date: 2022-10-11 14:51+0000\n" +"Last-Translator: Ronak Patel \n" +"Language-Team: German \n" +"Language: de_DE@informal\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.11.2\n" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:2 +msgid "Email Recovery" +msgstr "E-Mail-Wiederherstellung" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:3 +msgid "Email Recovery App" +msgstr "E-Mail-Wiederherstellungsapp" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:3 +msgid "Recovery Email" +msgstr "Wiederherstellungs-E-Mail" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:7 +msgid "Recovery Email " +msgstr "Wiederherstellungs-E-Mail " + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:22 +msgid "Changes saved" +msgstr "Änderungen gespeichert" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:26 +msgid "ErrorRecoveryEmailSameAsUserEmail" +msgstr "" diff --git a/translationfiles/es/email-recovery.po b/translationfiles/es/email-recovery.po new file mode 100644 index 0000000000000000000000000000000000000000..9c56dfc7e9636d6e03fa4f2d0ee3e36d120c3bed --- /dev/null +++ b/translationfiles/es/email-recovery.po @@ -0,0 +1,44 @@ +# BASE TRANSLATION FILE. +# Copyright (C) 2022 MURENA +# This file is distributed under the same license as the Nextcloud package. +# dev@murena.io +# +msgid "" +msgstr "" +"Project-Id-Version: Nextcloud 3.14159\n" +"Report-Msgid-Bugs-To: translations\\@example.com\n" +"POT-Creation-Date: 2022-05-06 19:52+0530\n" +"PO-Revision-Date: 2022-10-11 14:51+0000\n" +"Last-Translator: Ronak Patel \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.11.2\n" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:2 +msgid "Email Recovery" +msgstr "Recuperación del correo electrónico" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:3 +msgid "Email Recovery App" +msgstr "Aplicación de recuperación de correo electrónico" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:3 +msgid "Recovery Email" +msgstr "Correo electrónico de recuperación" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:7 +msgid "Recovery Email " +msgstr "Correo electrónico de recuperación " + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:22 +msgid "Changes saved" +msgstr "Cambios guardados" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:26 +msgid "ErrorRecoveryEmailSameAsUserEmail" +msgstr "" diff --git a/translationfiles/fr/email-recovery.po b/translationfiles/fr/email-recovery.po new file mode 100644 index 0000000000000000000000000000000000000000..e5506c863ed6438228aeb45ba235277f48b29f11 --- /dev/null +++ b/translationfiles/fr/email-recovery.po @@ -0,0 +1,44 @@ +# BASE TRANSLATION FILE. +# Copyright (C) 2022 MURENA +# This file is distributed under the same license as the Nextcloud package. +# dev@murena.io +# +msgid "" +msgstr "" +"Project-Id-Version: Nextcloud 3.14159\n" +"Report-Msgid-Bugs-To: translations\\@example.com\n" +"POT-Creation-Date: 2022-05-06 19:52+0530\n" +"PO-Revision-Date: 2022-10-11 14:51+0000\n" +"Last-Translator: Ronak Patel \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.11.2\n" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:2 +msgid "Email Recovery" +msgstr "Récupération des e-mails" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:3 +msgid "Email Recovery App" +msgstr "Application de récupération des e-mails" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:3 +msgid "Recovery Email" +msgstr "Courriel de récupération" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:7 +msgid "Recovery Email " +msgstr "Courriel de récupération " + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:22 +msgid "Changes saved" +msgstr "Changements enregistrés" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:26 +msgid "ErrorRecoveryEmailSameAsUserEmail" +msgstr "" diff --git a/translationfiles/it/email-recovery.po b/translationfiles/it/email-recovery.po new file mode 100644 index 0000000000000000000000000000000000000000..ccb48fbc00c7e2fa74240c6eebb0a554c8613574 --- /dev/null +++ b/translationfiles/it/email-recovery.po @@ -0,0 +1,44 @@ +# BASE TRANSLATION FILE. +# Copyright (C) 2022 MURENA +# This file is distributed under the same license as the Nextcloud package. +# dev@murena.io +# +msgid "" +msgstr "" +"Project-Id-Version: Nextcloud 3.14159\n" +"Report-Msgid-Bugs-To: translations\\@example.com\n" +"POT-Creation-Date: 2022-05-06 19:52+0530\n" +"PO-Revision-Date: 2022-10-11 14:51+0000\n" +"Last-Translator: Ronak Patel \n" +"Language-Team: Italian \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.11.2\n" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:2 +msgid "Email Recovery" +msgstr "Recupero delle e-mail" + +#: /home/nivesh/Desktop/repos/email-recovery/specialAppInfoFakeDummyForL10nScript.php:3 +msgid "Email Recovery App" +msgstr "App per il recupero delle e-mail" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:3 +msgid "Recovery Email" +msgstr "Recupero e-mail" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:7 +msgid "Recovery Email " +msgstr "Recupero e-mail " + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:22 +msgid "Changes saved" +msgstr "Modifiche salvate" + +#: /home/nivesh/Desktop/repos/email-recovery/src/main.html:26 +msgid "ErrorRecoveryEmailSameAsUserEmail" +msgstr ""