Compare commits

...

3 Commits

Author SHA1 Message Date
Weblate (bot)
f32448b9f5 Translated using Weblate (#3819)
Co-authored-by: Anders Johansson <johansson@aljmedia.se>
2023-02-05 20:14:28 +01:00
Weblate (bot)
f3b4078c6a Translated using Weblate (#3803)
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Roya K <royakhajavi@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: gnu-ewm <gnu.ewm@protonmail.com>
2023-02-05 20:07:12 +01:00
Kevin Papst
ca846b5fbf 2.0 RC 1 - new ConfigurationService (#3810)
* use html5 email validation
* remove static cache seed, for compatibility with non default (file based) caches
* added caching ConfigurationService, removed use of doctrine result cache
* simplify configuration API
2023-02-05 19:51:08 +01:00
30 changed files with 432 additions and 195 deletions

View File

@@ -1,11 +1,10 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
prefix_seed: "kimai"
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
#default_redis_provider: redis://127.0.0.1:6379
#app: cache.adapter.memcached
#default_memcached_provider: 'memcached://localhost'
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu

View File

@@ -1,6 +1,4 @@
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: auto

View File

@@ -7,12 +7,7 @@ services:
public: true
App\Configuration\SystemConfiguration:
arguments: ['@App\Repository\ConfigurationRepository', "%kimai.config%"]
App\Repository\ConfigurationRepository:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\Configuration']
arguments: ['@App\Configuration\ConfigurationService', "%kimai.config%"]
# required for the importer command test
App\Repository\UserRepository:

View File

@@ -915,36 +915,16 @@ parameters:
count: 1
path: src/Configuration/SystemConfiguration.php
-
message: "#^Offset \\(int\\|string\\) might not exist on array\\|null\\.$#"
count: 1
path: src/Configuration/SystemConfiguration.php
-
message: "#^Parameter \\#1 \\$array of function array_filter expects array, array\\|null given\\.$#"
count: 2
path: src/Configuration/SystemConfiguration.php
-
message: "#^Parameter \\#1 \\$key of method App\\\\Configuration\\\\SystemConfiguration\\:\\:set\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Configuration/SystemConfiguration.php
-
message: "#^Parameter \\#1 \\$key of method App\\\\Configuration\\\\SystemConfiguration\\:\\:set\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/Configuration/SystemConfiguration.php
-
message: "#^Parameter \\#1 \\$string of function trim expects string, bool\\|float\\|int\\|string given\\.$#"
count: 1
path: src/Configuration/SystemConfiguration.php
-
message: "#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\|null given\\.$#"
count: 3
path: src/Configuration/SystemConfiguration.php
-
message: "#^Method App\\\\Controller\\\\AbstractController\\:\\:createFormForGetRequest\\(\\) has parameter \\$data with no type specified\\.$#"
count: 1
@@ -1615,11 +1595,6 @@ parameters:
count: 3
path: src/Controller/SystemConfigurationController.php
-
message: "#^Parameter \\#1 \\$model of method App\\\\Repository\\\\ConfigurationRepository\\:\\:saveSystemConfiguration\\(\\) expects App\\\\Form\\\\Model\\\\SystemConfiguration, mixed given\\.$#"
count: 1
path: src/Controller/SystemConfigurationController.php
-
message: "#^Parameter \\#1 \\$value of method App\\\\Form\\\\Model\\\\Configuration\\:\\:setValue\\(\\) expects bool\\|int\\|object\\|string\\|null, bool\\|float\\|int\\|string given\\.$#"
count: 1
@@ -6360,11 +6335,6 @@ parameters:
count: 1
path: src/Repository/BookmarkRepository.php
-
message: "#^Method App\\\\Repository\\\\ConfigurationRepository\\:\\:saveSystemConfiguration\\(\\) has no return type specified\\.$#"
count: 1
path: src/Repository/ConfigurationRepository.php
-
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
count: 1

View File

@@ -9,18 +9,13 @@
namespace App\Configuration;
use App\Entity\Configuration;
/**
* @internal
*/
interface ConfigLoaderInterface
{
/**
* @param string $name
* @return ?Configuration
*/
public function getConfiguration(string $name): ?Configuration;
/**
* @return Configuration[]
* @return array<string, string|null>
*/
public function getConfigurations(): array;
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Configuration;
use App\Entity\Configuration;
use App\Form\Model\SystemConfiguration;
use App\Repository\ConfigurationRepository;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
final class ConfigurationService implements ConfigLoaderInterface
{
/**
* @var array<string, string|null>
*/
private static array $cacheAll = [];
private static bool $initialized = false;
public function __construct(private ConfigurationRepository $configurationRepository, private CacheInterface $cache)
{
}
/**
* @return array<string, string|null>
*/
public function getConfigurations(): array
{
if (self::$initialized === true) {
return self::$cacheAll;
}
self::$cacheAll = $this->cache->get('configurations', function (ItemInterface $item) {
$item->expiresAfter(86400); // one day
return $this->configurationRepository->getConfigurations();
});
self::$initialized = true;
return self::$cacheAll;
}
public function getConfiguration(string $name): ?Configuration
{
return $this->configurationRepository->findOneBy(['name' => $name]);
}
public function clearCache(): void
{
$this->cache->delete('configurations');
self::$initialized = false;
}
public function saveConfiguration(Configuration $configuration): void
{
$this->configurationRepository->saveConfiguration($configuration);
$this->clearCache();
}
public function saveSystemConfiguration(SystemConfiguration $model): void
{
$this->configurationRepository->saveSystemConfiguration($model);
$this->clearCache();
}
}

View File

@@ -13,7 +13,7 @@ final class SystemConfiguration
{
private bool $initialized = false;
public function __construct(private ConfigLoaderInterface $repository, private ?array $settings)
public function __construct(private ConfigLoaderInterface $repository, private array $settings = [])
{
}
@@ -23,8 +23,8 @@ final class SystemConfiguration
return;
}
foreach ($this->repository->getConfigurations() as $configuration) {
$this->set($configuration->getName(), $configuration->getValue());
foreach ($this->repository->getConfigurations() as $key => $value) {
$this->set($key, $value);
}
$this->initialized = true;
@@ -35,7 +35,6 @@ final class SystemConfiguration
*
* If no key is given to the method, the entire array will be replaced.
*
* @see https://github.com/divineomega/array_undot
* @param string $key
* @param mixed $value
* @return void
@@ -70,6 +69,7 @@ final class SystemConfiguration
/**
* This method should be avoided if possible, use plain keys instead.
*
* @see https://github.com/divineomega/array_undot
* @param string $key
* @return array
*/
@@ -219,7 +219,7 @@ final class SystemConfiguration
}
/**
* @return array<mixed>
* @return array<int, array<'saml'|'kimai', string>>
*/
public function getSamlRolesMapping(): array
{
@@ -227,7 +227,7 @@ final class SystemConfiguration
}
/**
* @return array<mixed>
* @return array<string, array<mixed>|bool>
*/
public function getSamlConnection(): array
{
@@ -235,7 +235,7 @@ final class SystemConfiguration
}
/**
* @return array<mixed>
* @return array<int, array<'saml'|'kimai', string>>
*/
public function getSamlAttributeMapping(): array
{

View File

@@ -9,6 +9,7 @@
namespace App\Controller;
use App\Configuration\ConfigurationService;
use App\Configuration\SystemConfiguration;
use App\Event\SystemConfigurationEvent;
use App\Form\Model\Configuration;
@@ -30,7 +31,6 @@ use App\Form\Type\TimezoneType;
use App\Form\Type\TrackingModeType;
use App\Form\Type\WeekDaysType;
use App\Form\Type\YesNoType;
use App\Repository\ConfigurationRepository;
use App\Timesheet\LockdownService;
use App\Utils\PageSetup;
use App\Validator\Constraints\ColorChoices;
@@ -61,7 +61,7 @@ use Symfony\Component\Validator\Constraints\Regex;
#[IsGranted('system_configuration')]
final class SystemConfigurationController extends AbstractController
{
public function __construct(private EventDispatcherInterface $eventDispatcher, private ConfigurationRepository $repository, private SystemConfiguration $systemConfiguration, private LockdownService $lockdownService)
public function __construct(private EventDispatcherInterface $eventDispatcher, private ConfigurationService $repository, private SystemConfiguration $systemConfiguration, private LockdownService $lockdownService)
{
}
@@ -142,7 +142,9 @@ final class SystemConfigurationController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
try {
$this->repository->saveSystemConfiguration($form->getData());
/** @var SystemConfigurationModel $saveModel */
$saveModel = $form->getData();
$this->repository->saveSystemConfiguration($saveModel);
$this->flashSuccess('action.update.success');
} catch (\Exception $ex) {
$this->handleFormUpdateException($ex, $form);

View File

@@ -21,7 +21,7 @@ final class MailType extends AbstractType
$resolver->setDefaults([
'label' => 'email',
'constraints' => [
new Email(['mode' => 'loose'])
new Email(['mode' => 'html5'])
],
]);
}

View File

@@ -9,7 +9,6 @@
namespace App\Repository;
use App\Configuration\ConfigLoaderInterface;
use App\Entity\Configuration;
use App\Form\Model\SystemConfiguration;
use Doctrine\ORM\EntityRepository;
@@ -17,74 +16,36 @@ use Doctrine\ORM\Exception\ORMException;
/**
* @extends EntityRepository<Configuration>
* @internal use App\Configuration\ConfigurationService instead
* @final
*/
class ConfigurationRepository extends EntityRepository implements ConfigLoaderInterface
class ConfigurationRepository extends EntityRepository
{
private const CACHE_KEY = 'ConfigurationRepository_All';
/**
* @var array<string, Configuration>
*/
private static array $cacheAll = [];
private static bool $initialized = false;
public function clearCache(): void
{
self::$cacheAll = [];
self::$initialized = false;
$cache = $this->getEntityManager()->getConfiguration()->getResultCache();
if ($cache !== null && $cache->hasItem(self::CACHE_KEY)) {
$cache->deleteItem(self::CACHE_KEY);
}
}
private function prefillCache(): void
{
if (self::$initialized === true) {
return;
}
$query = $this->createQueryBuilder('s')->getQuery();
$query->enableResultCache(86400, self::CACHE_KEY);
$configs = $query->getResult();
foreach ($configs as $config) {
self::$cacheAll[$config->getName()] = $config;
}
self::$initialized = true;
}
public function saveConfiguration(Configuration $configuration): void
{
$entityManager = $this->getEntityManager();
$entityManager->persist($configuration);
$entityManager->flush();
$this->clearCache();
}
/**
* @return Configuration[]
* @return array<string, string>
*/
public function getConfigurations(): array
{
$this->prefillCache();
$query = $this->createQueryBuilder('s')->select('s.name')->addSelect('s.value')->getQuery();
/** @var array<int, array<'name'|'value', string>> $result */
$result = $query->getArrayResult();
return array_values(self::$cacheAll);
}
public function getConfiguration(string $name): ?Configuration
{
$this->prefillCache();
if (!\array_key_exists($name, self::$cacheAll)) {
return null;
$all = [];
foreach ($result as $row) {
$all[$row['name']] = $row['value'];
}
return self::$cacheAll[$name];
return $all;
}
public function saveSystemConfiguration(SystemConfiguration $model)
public function saveSystemConfiguration(SystemConfiguration $model): void
{
$em = $this->getEntityManager();
$em->beginTransaction();
@@ -120,7 +81,5 @@ class ConfigurationRepository extends EntityRepository implements ConfigLoaderIn
$em->rollback();
throw $ex;
}
$this->clearCache();
}
}

View File

@@ -18,29 +18,22 @@ use App\Entity\Configuration;
class TestConfigLoader implements ConfigLoaderInterface
{
/**
* @var Configuration[]
* @var array<string, string|null>
*/
private array $configs;
private array $configs = [];
/**
* @param Configuration[] $configs
*/
public function __construct(array $configs)
{
$this->configs = $configs;
}
public function getConfiguration(string $name): ?Configuration
{
if (!\array_key_exists($name, $this->configs)) {
return null;
foreach ($configs as $config) {
$this->configs[$config->getName()] = $config->getValue();
}
return $this->configs[$name];
}
/**
* @return Configuration[]
* @return array<string, string|null>
*/
public function getConfigurations(): array
{

View File

@@ -9,11 +9,11 @@
namespace App\Tests\Controller;
use App\Configuration\ConfigurationService;
use App\DataFixtures\UserFixtures;
use App\Entity\Configuration;
use App\Entity\User;
use App\Form\Type\DateRangeType;
use App\Repository\ConfigurationRepository;
use App\Repository\UserRepository;
use App\Tests\KernelTestTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
@@ -89,9 +89,10 @@ abstract class ControllerBaseTest extends WebTestCase
protected function setSystemConfiguration(string $name, $value): void
{
$repository = self::getContainer()->get(ConfigurationRepository::class);
/** @var ConfigurationService $repository */
$repository = self::getContainer()->get(ConfigurationService::class);
$entity = $repository->findOneBy(['name' => $name]);
$entity = $repository->getConfiguration($name);
if ($entity === null) {
$entity = new Configuration();
$entity->setName($name);
@@ -103,9 +104,9 @@ abstract class ControllerBaseTest extends WebTestCase
protected function clearConfigCache()
{
/** @var ConfigurationRepository $repository */
$repository = self::getContainer()->get(ConfigurationRepository::class);
$repository->clearCache();
/** @var ConfigurationService $service */
$service = self::getContainer()->get(ConfigurationService::class);
$service->clearCache();
}
protected function getClientForAuthenticatedUser(string $role = User::ROLE_USER): HttpKernelBrowser

View File

@@ -10,11 +10,9 @@
namespace App\Tests\Controller;
use App\Entity\Activity;
use App\Entity\Configuration;
use App\Entity\Timesheet;
use App\Entity\TimesheetMeta;
use App\Entity\User;
use App\Repository\ConfigurationRepository;
use App\Repository\TagRepository;
use App\Tests\DataFixtures\ActivityFixtures;
use App\Tests\DataFixtures\TagFixtures;
@@ -442,12 +440,7 @@ class TimesheetControllerTest extends ControllerBaseTest
$response = $client->getResponse();
$this->assertTrue($response->isSuccessful());
/** @var ConfigurationRepository $repository */
$repository = $this->getEntityManager()->getRepository(Configuration::class);
$config = new Configuration();
$config->setName('timesheet.rules.allow_overbooking_budget');
$config->setValue(false);
$repository->saveConfiguration($config);
$this->setSystemConfiguration('timesheet.rules.allow_overbooking_budget', false);
$this->assertHasValidationError(
$client,
@@ -496,12 +489,7 @@ class TimesheetControllerTest extends ControllerBaseTest
$response = $client->getResponse();
$this->assertTrue($response->isSuccessful());
/** @var ConfigurationRepository $repository */
$repository = $this->getEntityManager()->getRepository(Configuration::class);
$config = new Configuration();
$config->setName('timesheet.rules.allow_zero_duration');
$config->setValue(false);
$repository->saveConfiguration($config);
$this->setSystemConfiguration('timesheet.rules.allow_zero_duration', false);
$this->assertHasValidationError(
$client,

View File

@@ -2867,11 +2867,6 @@ parameters:
count: 4
path: Controller/CalendarControllerTest.php
-
message: "#^Cannot call method findOneBy\\(\\) on object\\|null\\.$#"
count: 1
path: Controller/ControllerBaseTest.php
-
message: "#^Cannot call method getRepository\\(\\) on object\\|null\\.$#"
count: 1
@@ -2887,11 +2882,6 @@ parameters:
count: 1
path: Controller/ControllerBaseTest.php
-
message: "#^Cannot call method saveConfiguration\\(\\) on object\\|null\\.$#"
count: 1
path: Controller/ControllerBaseTest.php
-
message: "#^Method App\\\\Tests\\\\Controller\\\\ControllerBaseTest\\:\\:assert404\\(\\) has no return type specified\\.$#"
count: 1

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="de" datatype="plaintext" original="about.en.xlf">
<body>
<trans-unit id="oYYDCG5" resname="support">
<source>support</source>
<target>Forum</target>
<source>Support</source>
<target state="needs-translation">Support</target>
</trans-unit>
<trans-unit id="dHqPOYO" approved="yes" resname="website">
<source>website</source>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="en" datatype="plaintext" original="about.en.xlf">
<body>
<trans-unit id="oYYDCG5" resname="support">
<source>support</source>
<target>Forum</target>
<source>Support</source>
<target state="translated">Support</target>
</trans-unit>
<trans-unit id="dHqPOYO" resname="website">
<source>website</source>

View File

@@ -16,7 +16,7 @@
</trans-unit>
<trans-unit id="BPiZbad" resname="user">
<source>user</source>
<target>تم تجميع المستخدم - إدخال واحد لكل مستخدم</target>
<target state="translated">مجموعة مستخدمين: إدخال واحد لكل مستخدم</target>
</trans-unit>
<trans-unit id="aDtTH0H" resname="price">
<source>price</source>

View File

@@ -42,6 +42,10 @@
<source>programmatic</source>
<target state="translated">لمزيد من المعالجة</target>
</trans-unit>
<trans-unit id="22pt8Gs" resname="download_invoice_renderer">
<source>download_invoice_renderer</source>
<target state="translated">يمكنك تنزيل المزيد من النماذج هنا</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -42,6 +42,10 @@
<source>text</source>
<target>Text</target>
</trans-unit>
<trans-unit id="22pt8Gs" resname="download_invoice_renderer">
<source>download_invoice_renderer</source>
<target state="translated">Puteți descărca alte modele aici</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -286,7 +286,7 @@
</trans-unit>
<trans-unit id="MQKiG33" resname="profile.preferences">
<source>profile.preferences</source>
<target>تفضيلات</target>
<target state="translated">التفضيلات</target>
</trans-unit>
<!--
Admin: Timesheet
@@ -698,7 +698,7 @@
</trans-unit>
<trans-unit id="hBNESu6" resname="my.profile">
<source>my.profile</source>
<target>ملف التعريف الخاص بي</target>
<target state="translated">ملفي التعريفي</target>
</trans-unit>
<trans-unit id="JBkykGe" resname="search">
<source>search</source>

View File

@@ -7,7 +7,7 @@
-->
<trans-unit id="N7mikjJ" resname="time_tracking">
<source>time_tracking</source>
<target>رهگیری زمان</target>
<target state="translated">ردیابی زمان</target>
</trans-unit>
<trans-unit id="inmIkP6" resname="yes">
<source>yes</source>
@@ -35,7 +35,7 @@
</trans-unit>
<trans-unit id="vDd3alC" resname="admin_entity.delete_confirm">
<source>admin_entity.delete_confirm</source>
<target>این داده به خوبی پاک خواهد شد! متناوبا، از سوی دیگر شما میتوانید یک ورودی را انتخاب نمایید که تمام داده ها به آن ترجمه شوند:</target>
<target state="translated">این داده ها نیز حذف خواهند شد! از طرف دیگر، می توانید یک ورودی را انتخاب کنید که تمام داده ها به آن منتقل شوند:</target>
</trans-unit>
<trans-unit id="COyxNde" resname="delete.not_in_use">
<source>delete.not_in_use</source>
@@ -75,11 +75,11 @@
</trans-unit>
<trans-unit id="0M_Gylq" resname="rates.empty">
<source>rates.empty</source>
<target>فایلی هنوز تنظیم نشده است.</target>
<target state="translated">هیچ قیمتی تنظیم نشده است</target>
</trans-unit>
<trans-unit id="mflwMcX" resname="rates.title">
<source>rates.title</source>
<target>هزینه ها</target>
<target state="translated">قیمت ها</target>
</trans-unit>
<trans-unit id="zFfl7jw" resname="sum.total">
<source>sum.total</source>
@@ -110,11 +110,11 @@
</trans-unit>
<trans-unit id="aq59Sk8" resname="api_token_repeat">
<source>api_token_repeat</source>
<target>ریست پسورد API</target>
<target state="translated">تکرار پسورد API</target>
</trans-unit>
<trans-unit id=".372.o7" resname="login_required">
<source>login_required</source>
<target>مجوز را گم کرده. به صفحه ورود انتقال دهد؟</target>
<target state="translated">مجوز از دست رفته است. تغییر مسیر برای ورود به سیستم؟</target>
</trans-unit>
<!--
Menu / Navbar items
@@ -349,7 +349,7 @@
</trans-unit>
<trans-unit id="yxQop43" resname="all_times">
<source>all_times</source>
<target>برنامه های زمانی</target>
<target state="translated">همیشه</target>
</trans-unit>
<trans-unit id="ypVQO7o" resname="menu.reporting">
<source>menu.reporting</source>
@@ -404,8 +404,8 @@
<target>رنگ</target>
</trans-unit>
<trans-unit id="GzWKgBk" resname="error.directory_missing">
<source>error.directory_missing</source>
<target>موقعیت "%dir%" وجود ندارد و قابل ساختن نیست.</target>
<source>The directory “%dir%” does not exist and could not be created.</source>
<target state="translated">دایرکتوری "%dir%" وجود ندارد و نمی تواند ایجاد شود.</target>
</trans-unit>
<trans-unit id="C2TGff0" resname="error.directory_protected">
<source>error.directory_protected</source>
@@ -484,8 +484,8 @@
<target>حذف جستجوی علاقه مندی</target>
</trans-unit>
<trans-unit id=".ndupSK" resname="registration.check_email">
<source>registration.check_email</source>
<target>ایمیلی برای %email% شامل لینک فعالسازی ارسال شد. جهت فعال کردن حساب کاربری خود باید بر روی آن کلیک کنید.</target>
<source>An e-mail has been sent to %email%. It contains a link you must click to activate your account.</source>
<target state="translated">ایمیلی شامل لینک فعالسازی به %email% ارسال شد. جهت فعال کردن حساب کاربری خود باید بر روی آن کلیک کنید.</target>
</trans-unit>
<trans-unit id="_cY.wHP" resname="resetting.check_email">
<source>resetting.check_email</source>
@@ -1365,11 +1365,11 @@
</trans-unit>
<trans-unit id="wYcx0So" resname="search.no_results">
<source>search.no_results</source>
<target state="needs-translation">No results found for "%input%"</target>
<target state="translated">هیچ نتیجه ای برای "%input%" یافت نشد</target>
</trans-unit>
<trans-unit id="biHjm0h" resname="select.add_new">
<source>select.add_new</source>
<target state="needs-translation">ایجاد جدید با نام "%input%"</target>
<target state="translated">ایجاد نام جدید با "%input%"</target>
</trans-unit>
<trans-unit id="dru_Thv" resname="please_choose">
<source>please_choose</source>

View File

@@ -980,10 +980,6 @@
<source>export</source>
<target>Izvoz</target>
</trans-unit>
<trans-unit id="KxuVIAw" resname="export.subtitle">
<source>export.subtitle</source>
<target>Izvezi podatke vremenske tablice u razne formate</target>
</trans-unit>
<trans-unit id="iwWaUoa" resname="export.filter">
<source>export.filter</source>
<target>Filtriraj podatke za izvoz</target>

View File

@@ -81,8 +81,8 @@
Login / Security
-->
<trans-unit id="XohImNo" resname="password">
<source>password</source>
<target state="needs-translation">Password</target>
<source>Password</source>
<target state="translated">Password</target>
</trans-unit>
<trans-unit id="hLEZkoC" resname="password_repeat">
<source>password_repeat</source>

View File

@@ -1222,6 +1222,178 @@
<source>Apps</source>
<target state="translated">Aplikacje</target>
</trans-unit>
<trans-unit id="3rJ5QDP" resname="help.teams">
<source>help.teams</source>
<target state="translated">Ogranicza widoczność do wybranych zespołów.</target>
</trans-unit>
<trans-unit id="FgKTw5v" resname="login.2fa_label">
<source>login.2fa_label</source>
<target state="translated">Kod potwierdzający</target>
</trans-unit>
<trans-unit id="3WHV.yf" resname="activated">
<source>Activated</source>
<target state="translated">Aktywowane</target>
</trans-unit>
<trans-unit id="IDDfzoR" resname="invoiceLabel">
<source>invoiceLabel</source>
<target state="translated">Tekst na fakturze</target>
</trans-unit>
<trans-unit id="On18MKP" resname="plugin.installed">
<source>plugin.installed</source>
<target state="translated">Zainstalowane wtyczki</target>
</trans-unit>
<trans-unit id="ojfv7Jl" resname="invoice_form.title">
<source>invoice_form.title</source>
<target state="translated">Utwórz fakturę</target>
</trans-unit>
<trans-unit id="mQMcndl" resname="stats.userAmountToday">
<source>stats.userAmountToday</source>
<target state="translated">Mój dzisiejszy przychód</target>
</trans-unit>
<trans-unit id="LDS9o5X" resname="stats.userAmountWeek">
<source>stats.userAmountWeek</source>
<target state="translated">Moje przychody w tym tygodniu</target>
</trans-unit>
<trans-unit id="EedlgkM" resname="stats.userDurationTotal">
<source>stats.userDurationTotal</source>
<target state="translated">Moje całkowite godziny pracy</target>
</trans-unit>
<trans-unit id="LDsHBXd" resname="modal.columns.label">
<source>modal.columns.label</source>
<target state="translated">Kolumny tabeli</target>
</trans-unit>
<trans-unit id="FeFBn_3" resname="profile.2fa">
<source>profile.2fa</source>
<target state="translated">Dwuskładnikowe (TOTP)</target>
</trans-unit>
<trans-unit id="o0hsx2H" resname="mark_as_open">
<source>mark_as_open</source>
<target state="translated">Oznacz jako otwarte</target>
</trans-unit>
<trans-unit id="EI5gPkp" resname="stats.userDurationToday">
<source>stats.userDurationToday</source>
<target state="translated">Moje dzisiejsze godziny pracy</target>
</trans-unit>
<trans-unit id="9jlbhkf" resname="stats.userAmountTotal">
<source>stats.userAmountTotal</source>
<target state="translated">Moje całkowite przychody</target>
</trans-unit>
<trans-unit id="E3Kj9lt" resname="stats.userAmountMonth">
<source>stats.userAmountMonth</source>
<target state="translated">Moje przychody w tym miesiącu</target>
</trans-unit>
<trans-unit id="xvdsZsS" resname="stats.userAmountYear">
<source>stats.userAmountYear</source>
<target state="translated">Moje przychody w tym roku</target>
</trans-unit>
<trans-unit id="DXcvOMM" resname="stats.userDurationWeek">
<source>stats.userDurationWeek</source>
<target state="translated">Moje godziny pracy w tym tygodniu</target>
</trans-unit>
<trans-unit id="0Hhg4zQ" resname="system_account.help">
<source>system_account.help</source>
<target state="translated">Będzie ukryty w raportach i listach rozwijanych</target>
</trans-unit>
<trans-unit id="0WpgaGa" resname="stats.userDurationMonth">
<source>stats.userDurationMonth</source>
<target state="translated">Moje godziny pracy w tym miesiącu</target>
</trans-unit>
<trans-unit id="BjyLITj" resname="stats.userDurationYear">
<source>stats.userDurationYear</source>
<target state="translated">Moje godziny pracy w tym roku</target>
</trans-unit>
<trans-unit id="wYcx0So" resname="search.no_results">
<source>search.no_results</source>
<target state="translated">Nie znaleziono żadnych wyników dla "%input%"</target>
</trans-unit>
<trans-unit id="2lL9ACs" resname="help.invoiceTemplate">
<source>help.invoiceTemplate</source>
<target state="translated">Faktura jest generowana za pomocą tego szablonu.</target>
</trans-unit>
<trans-unit id="5kFod8o" resname="team.configure_teamlead.help">
<source>team.configure_teamlead.help</source>
<target state="translated">Wybrani / wyróżnieni użytkownicy są liderami zespołu.</target>
</trans-unit>
<trans-unit id="biHjm0h" resname="select.add_new">
<source>select.add_new</source>
<target state="translated">Utwórz nowy o nazwie "%input%"</target>
</trans-unit>
<trans-unit id="JSBReoa" resname="api_password.intro">
<source>api_password.intro</source>
<target state="translated">Hasło interfejsu API jest używane do komunikacji pomiędzy Kimai a Twoimi aplikacjami</target>
</trans-unit>
<trans-unit id="hPY2VcD" resname="profile.2fa_intro">
<source>profile.2fa_intro</source>
<target state="translated">Nowe urządzenie można aktywować, skanując obraz w aplikacji.</target>
</trans-unit>
<trans-unit id="wwrREDg" resname="pagination">
<source>pagination</source>
<target state="translated">Pokaż wpisy %start% do %end% z %total%.</target>
</trans-unit>
<trans-unit id="IRzPWmL" resname="login.2fa_intro">
<source>login.2fa_intro</source>
<target state="translated">Proszę wpisać kod potwierdzający, aby zakończyć proces logowania.</target>
</trans-unit>
<trans-unit id="aGk9Aqt" resname="desktop">
<source>desktop</source>
<target state="translated">Komputer</target>
</trans-unit>
<trans-unit id="Y0PRG9Z" resname="profile.2fa_confirmation">
<source>profile.2fa_confirmation</source>
<target state="translated">Potwierdź kod, który aktywuje uwierzytelnianie dwuskładnikowe dla tego konta.</target>
</trans-unit>
<trans-unit id="SdLZTel" resname="favorite_routes">
<source>favorite_routes</source>
<target state="translated">Ulubione</target>
</trans-unit>
<trans-unit id="i7aSJ1i" resname="notifications">
<source>notifications</source>
<target state="translated">Powiadomienia</target>
</trans-unit>
<trans-unit id="0ZWvn12" resname="notifications.welcome">
<source>notifications.welcome</source>
<target state="translated">Dziękujemy za wyrażenie zgody na powiadomienia!</target>
</trans-unit>
<trans-unit id="PLLl1E0" resname="system_account">
<source>system_account</source>
<target state="translated">Konto systemowe</target>
</trans-unit>
<trans-unit id="t.jgy7A" resname="all_invoices">
<source>all_invoices</source>
<target state="translated">Historia faktur</target>
</trans-unit>
<trans-unit id="5NugIxO" resname="includeWithBudget">
<source>includeWithBudget</source>
<target state="translated">Pokaż wpisy z budżetem</target>
</trans-unit>
<trans-unit id="WOFPb2i" resname="skin.dark">
<source>skin.dark</source>
<target state="translated">Ciemny</target>
</trans-unit>
<trans-unit id="mjKOAgQ" resname="skin.light">
<source>skin.light</source>
<target state="translated">Jasny</target>
</trans-unit>
<trans-unit id="dru_Thv" resname="please_choose">
<source>please_choose</source>
<target state="translated">Proszę wybrać</target>
</trans-unit>
<trans-unit id="WbT2QLP" resname="extended_settings">
<source>extended_settings</source>
<target state="translated">Ustawienia rozszerzone</target>
</trans-unit>
<trans-unit id="ZZaqTEr" resname="no_activity">
<source>no_activity</source>
<target state="translated">Bez aktywności</target>
</trans-unit>
<trans-unit id="QT8.BCN" resname="api_password.missing_title">
<source>api_password.missing_title</source>
<target state="translated">Nie można użyć interfejsu API</target>
</trans-unit>
<trans-unit id="Uu8YvYV" resname="deactivated">
<source>Deactivated</source>
<target state="translated">Dezaktywowane</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -97,7 +97,7 @@
</trans-unit>
<trans-unit id="ebTjBb9" resname="my_times">
<source>my_times</source>
<target>Meus tempos</target>
<target state="translated">Meus horários</target>
</trans-unit>
<trans-unit id="yxQop43" resname="all_times">
<source>all_times</source>
@@ -271,7 +271,7 @@
-->
<trans-unit id="VXj0Y3h" resname="timesheet.title">
<source>timesheet.title</source>
<target>Meus Tempos</target>
<target state="translated">Meus horários</target>
</trans-unit>
<trans-unit id="dwEVXUR" resname="timesheet.edit">
<source>timesheet.edit</source>
@@ -476,23 +476,23 @@
-->
<trans-unit id="TdBJBAl" resname="stats.durationToday">
<source>stats.durationToday</source>
<target>Horas de trabalho hoje</target>
<target state="translated">Horas trabalhadas hoje</target>
</trans-unit>
<trans-unit id="XhKalZH" resname="stats.durationWeek">
<source>stats.durationWeek</source>
<target>Horas de trabalho esta semana</target>
<target state="translated">Horas trabalhadas esta semana</target>
</trans-unit>
<trans-unit id="uaOwf_P" resname="stats.durationMonth">
<source>stats.durationMonth</source>
<target>Horas de trabalho este mês</target>
<target state="translated">Horas trabalhadas este mês</target>
</trans-unit>
<trans-unit id="WqF84KR" resname="stats.durationYear">
<source>stats.durationYear</source>
<target>Horas de trabalho este ano</target>
<target state="translated">Horas trabalhadas este ano</target>
</trans-unit>
<trans-unit id="YtvPnl1" resname="stats.durationTotal">
<source>stats.durationTotal</source>
<target>Total de horas de trabalho</target>
<target state="translated">Total de horas trabalhadas</target>
</trans-unit>
<trans-unit id="IFOLMgp" resname="stats.yourWorkingHours">
<source>stats.yourWorkingHours</source>
@@ -1060,7 +1060,7 @@
</trans-unit>
<trans-unit id="xkugSAA" resname="stats.durationFinancialYear">
<source>stats.durationFinancialYear</source>
<target>Horário de trabalho neste ano fiscal</target>
<target state="translated">Horários de trabalho neste ano fiscal</target>
</trans-unit>
<trans-unit id="HWB4OGJ" resname="stats.workingTimeFinancialYear">
<source>stats.workingTimeFinancialYear</source>
@@ -1396,7 +1396,7 @@
</trans-unit>
<trans-unit id="wwrREDg" resname="pagination">
<source>pagination</source>
<target state="translated">Mostrar as entradas %start% a %end% do %total%.</target>
<target state="translated">Mostrar as entradas de %start% até %end% de %total%.</target>
</trans-unit>
<trans-unit id="mjKOAgQ" resname="skin.light">
<source>skin.light</source>

View File

@@ -1202,6 +1202,46 @@
<source>about.title</source>
<target>Om Kimai</target>
</trans-unit>
<trans-unit id="1MfS6X5" resname="remove_filter">
<source>remove_filter</source>
<target state="translated">Nollställ sökfilter</target>
</trans-unit>
<trans-unit id="fQ3Prt4" resname="security.unlock.title">
<source>security.unlock.title</source>
<target state="translated">Lås upp konto</target>
</trans-unit>
<trans-unit id="nqEYrfk" resname="security.unlock.intro">
<source>security.unlock.intro</source>
<target state="translated">Vänligen ange ditt lösenord för fullständig åtkomst</target>
</trans-unit>
<trans-unit id="853W5Iu" resname="security.unlock.button">
<source>security.unlock.button</source>
<target state="translated">Lås upp</target>
</trans-unit>
<trans-unit id="Eq2E_Rd" resname="menu.apps">
<source>Apps</source>
<target state="translated">Applikationer</target>
</trans-unit>
<trans-unit id="3rJ5QDP" resname="help.teams">
<source>help.teams</source>
<target state="translated">Begränsar synligheten till de valda lagen.</target>
</trans-unit>
<trans-unit id="aOJITgv" resname="help.invoiceText">
<source>help.invoiceText</source>
<target state="translated">Denna text används i fakturadokument och kan användas t.ex. att notera ytterligare leveransvillkor.</target>
</trans-unit>
<trans-unit id="IDDfzoR" resname="invoiceLabel">
<source>invoiceLabel</source>
<target state="translated">Fakturatext</target>
</trans-unit>
<trans-unit id="jLzX0Ae" resname="help.invoiceLabel">
<source>help.invoiceLabel</source>
<target state="translated">Den här texten ersätter objektets namn, vilket tillåter mer detaljerade etiketter för fakturaartiklar.</target>
</trans-unit>
<trans-unit id="AeMGtJO" resname="help.visible">
<source>help.visible</source>
<target state="translated">Om inställningen är avstängd visas inte objektet i rullgardinsrutor och listvyer.</target>
</trans-unit>
</body>
</file>
</xliff>

6
translations/plugins.ro.xlf Executable file → Normal file
View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="ro" datatype="plaintext" original="plugins.en.xlf">
<body>
@@ -6,6 +6,10 @@
<source>plugin.none_installed</source>
<target>Nu există nici un plugin instalat.</target>
</trans-unit>
<trans-unit id="zVtBsSo" resname="plugin.marketplace">
<source>plugin.marketplace</source>
<target state="translated">Plugin-uri noi de pe piață</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -282,6 +282,14 @@
<source>calendar.title_pattern</source>
<target state="translated">عرض عناوين إدخالات التقويم</target>
</trans-unit>
<trans-unit id="LmOfuE5" resname="customer.number_format">
<source>customer.number_format</source>
<target state="translated">شكل رقم الزبون</target>
</trans-unit>
<trans-unit id="1cAQPNS" resname="customer.allow_duplicate_number">
<source>customer.allow_duplicate_number</source>
<target state="translated">السماح بتكرار رقم الحساب</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -290,6 +290,18 @@
<source>activity.allow_inline_create</source>
<target state="translated">Zezwalaj na tworzenie działań w formie śledzenia czasu</target>
</trans-unit>
<trans-unit id="LmOfuE5" resname="customer.number_format">
<source>customer.number_format</source>
<target state="translated">Format numeru klienta</target>
</trans-unit>
<trans-unit id="1cAQPNS" resname="customer.allow_duplicate_number">
<source>customer.allow_duplicate_number</source>
<target state="translated">Zezwalaj na duplikowanie numeru konta</target>
</trans-unit>
<trans-unit id="pBxkAF_" resname="copy_teams_on_create" approved="no">
<source>copy_teams_on_create</source>
<target state="translated">Przejmij zespoły od zalogowanego użytkownika podczas tworzenia nowych wpisów</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="ar" datatype="plaintext" original="wizard.en.xlf">
<body>
<trans-unit id="9XidDHG" resname="wizard.intro.title">
<source>wizard.intro.title</source>
<target state="translated">مرحبا بكم في Kimai</target>
</trans-unit>
<trans-unit id="X0ws5mu" resname="wizard.intro.description">
<source>wizard.intro.description</source>
<target state="translated">سيساعدك هذا المساعِد على تخصيص Kimai وفقا لاحتياجاتك.</target>
</trans-unit>
<trans-unit id="FCMK0ph" resname="wizard.separator">
<source>wizard.separator</source>
<target state="translated">إعداداتك</target>
</trans-unit>
<trans-unit id="8_e6ApF" resname="wizard.profile.title">
<source>wizard.profile.title</source>
<target state="translated">ملفك الشخصي</target>
</trans-unit>
<trans-unit id="Mo1506M" resname="wizard.profile.description">
<source>wizard.profile.description</source>
<target state="translated">بعض الإعدادات الأساسية لتسهيل عملك مع Kimai.</target>
</trans-unit>
<trans-unit id="8p.2pXa" resname="wizard.done.title">
<source>wizard.done.title</source>
<target state="translated">تهانينا</target>
</trans-unit>
<trans-unit id="k1SzMuI" resname="wizard.done.description">
<source>wizard.done.description</source>
<target state="translated">اكتملت الآن أهم الإعدادات. هناك العديد من الأشياء الأخرى التي يمكنك تخصيصها لاحقا في ملف تعريف المستخدم الخاص بك.</target>
</trans-unit>
</body>
</file>
</xliff>