support installation specific translations (#1099)
This commit is contained in:
@@ -81,6 +81,11 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
$renderer: !tagged widget.renderer
|
$renderer: !tagged widget.renderer
|
||||||
|
|
||||||
|
App\Utils\Translator:
|
||||||
|
decorates: translator
|
||||||
|
arguments:
|
||||||
|
$localDomains: '%kimai.i18n_domains%'
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# DATABASE
|
# DATABASE
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ class AppExtension extends Extension
|
|||||||
$container->setParameter('kimai.timesheet.rounding', $config['timesheet']['rounding']);
|
$container->setParameter('kimai.timesheet.rounding', $config['timesheet']['rounding']);
|
||||||
|
|
||||||
$this->setLdapParameter($config['ldap'], $container);
|
$this->setLdapParameter($config['ldap'], $container);
|
||||||
|
|
||||||
|
// translation files, which can overwrite the default kimai translations
|
||||||
|
$localTranslations = [];
|
||||||
|
if (null !== $config['theme']['branding']['translation']) {
|
||||||
|
$localTranslations[] = $config['theme']['branding']['translation'];
|
||||||
|
}
|
||||||
|
if (null !== $config['industry']['translation']) {
|
||||||
|
$localTranslations[] = $config['industry']['translation'];
|
||||||
|
}
|
||||||
|
$container->setParameter('kimai.i18n_domains', $localTranslations);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setLdapParameter(array $config, ContainerBuilder $container)
|
protected function setLdapParameter(array $config, ContainerBuilder $container)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->append($this->getLanguagesNode())
|
->append($this->getLanguagesNode())
|
||||||
->append($this->getCalendarNode())
|
->append($this->getCalendarNode())
|
||||||
->append($this->getThemeNode())
|
->append($this->getThemeNode())
|
||||||
|
->append($this->getIndustryNode())
|
||||||
->append($this->getDashboardNode())
|
->append($this->getDashboardNode())
|
||||||
->append($this->getWidgetsNode())
|
->append($this->getWidgetsNode())
|
||||||
->append($this->getDefaultsNode())
|
->append($this->getDefaultsNode())
|
||||||
@@ -344,6 +345,9 @@ class Configuration implements ConfigurationInterface
|
|||||||
->scalarNode('title')
|
->scalarNode('title')
|
||||||
->defaultNull()
|
->defaultNull()
|
||||||
->end()
|
->end()
|
||||||
|
->scalarNode('translation')
|
||||||
|
->defaultNull()
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->integerNode('autocomplete_chars')
|
->integerNode('autocomplete_chars')
|
||||||
@@ -355,6 +359,22 @@ class Configuration implements ConfigurationInterface
|
|||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getIndustryNode()
|
||||||
|
{
|
||||||
|
$builder = new TreeBuilder('industry');
|
||||||
|
/** @var ArrayNodeDefinition $rootNode */
|
||||||
|
$node = $builder->getRootNode();
|
||||||
|
|
||||||
|
$node
|
||||||
|
->addDefaultsIfNotSet()
|
||||||
|
->children()
|
||||||
|
->scalarNode('translation')->defaultNull()->end()
|
||||||
|
->end()
|
||||||
|
;
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getUserNode()
|
protected function getUserNode()
|
||||||
{
|
{
|
||||||
$builder = new TreeBuilder('user');
|
$builder = new TreeBuilder('user');
|
||||||
|
|||||||
@@ -340,11 +340,20 @@ class User extends BaseUser implements UserInterface
|
|||||||
return $this->hasRole(static::ROLE_ADMIN);
|
return $this->hasRole(static::ROLE_ADMIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName(): ?string
|
||||||
|
{
|
||||||
|
if (!empty($this->getAlias())) {
|
||||||
|
return $this->getAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return $this->getAlias() ?: $this->getUsername();
|
return $this->getDisplayName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,7 @@ class UserType extends AbstractType
|
|||||||
'class' => User::class,
|
'class' => User::class,
|
||||||
'label' => 'label.user',
|
'label' => 'label.user',
|
||||||
'choice_label' => function (User $user) {
|
'choice_label' => function (User $user) {
|
||||||
if (!empty($user->getAlias())) {
|
return $user->getDisplayName();
|
||||||
return $user->getAlias() . ' (' . $user->getUsername() . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user->getUsername();
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
128
src/Utils/Translator.php
Normal file
128
src/Utils/Translator.php
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?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\Utils;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;
|
||||||
|
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||||
|
use Symfony\Component\Translation\TranslatorBagInterface;
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
|
||||||
|
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be:
|
||||||
|
* class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
|
||||||
|
*
|
||||||
|
* But this is not compatible with PagerFanta yet.
|
||||||
|
*/
|
||||||
|
class Translator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var BaseTranslator
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $localDomains = [];
|
||||||
|
|
||||||
|
public function __construct(BaseTranslator $translator, array $localDomains = [])
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->localDomains = $localDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function trans($id, array $parameters = [], $domain = 'messages', $locale = null)
|
||||||
|
{
|
||||||
|
if (null === $domain) {
|
||||||
|
$domain = 'messages';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->localDomains as $localDomain) {
|
||||||
|
if (false !== $this->hasLocalOverwrite($id, $localDomain, $locale)) {
|
||||||
|
$domain = $localDomain;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->translator->trans($id, $parameters, $domain, $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasLocalOverwrite($id, $domain, $locale = null): bool
|
||||||
|
{
|
||||||
|
$found = false;
|
||||||
|
|
||||||
|
$catalogue = $this->getCatalogue($locale);
|
||||||
|
while (false === ($found = $catalogue->defines($id, $domain))) {
|
||||||
|
if ($cat = $catalogue->getFallbackCatalogue()) {
|
||||||
|
$catalogue = $cat;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the catalogue by locale.
|
||||||
|
*
|
||||||
|
* @param string|null $locale The locale or null to use the default
|
||||||
|
*
|
||||||
|
* @return MessageCatalogueInterface
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the locale contains invalid characters
|
||||||
|
*/
|
||||||
|
public function getCatalogue($locale = null)
|
||||||
|
{
|
||||||
|
return $this->translator->getCatalogue($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current locale.
|
||||||
|
*
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the locale contains invalid characters
|
||||||
|
*/
|
||||||
|
public function setLocale($locale)
|
||||||
|
{
|
||||||
|
$this->translator->setLocale($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current locale.
|
||||||
|
*
|
||||||
|
* @return string The locale
|
||||||
|
*/
|
||||||
|
public function getLocale()
|
||||||
|
{
|
||||||
|
return $this->translator->getLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given choice message by choosing a translation according to a number.
|
||||||
|
*
|
||||||
|
* @param string $id The message id (may also be an object that can be cast to string)
|
||||||
|
* @param int $number The number to use to find the index of the message
|
||||||
|
* @param array $parameters An array of parameters for the message
|
||||||
|
* @param string|null $domain The domain for the message or null to use the default
|
||||||
|
* @param string|null $locale The locale or null to use the default
|
||||||
|
*
|
||||||
|
* @return string The translated string
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the locale contains invalid characters
|
||||||
|
*/
|
||||||
|
public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
|
||||||
|
{
|
||||||
|
return $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro username(user) %}
|
{% macro username(user) %}
|
||||||
{{ user.alias|default(user.username) }}
|
{{- user.displayName -}}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro label_user(user) %}
|
{% macro label_user(user) %}
|
||||||
{% import _self as macro %}
|
{% import _self as macro %}
|
||||||
{{ macro.label(macro.username(user), 'primary') }}
|
{{ macro.label(user.displayName, 'primary') }}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro label_activity(activity, url) %}
|
{% macro label_activity(activity, url) %}
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ class AppExtensionTest extends TestCase
|
|||||||
'currency' => 'EUR',
|
'currency' => 'EUR',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
'kimai.theme' => [
|
'kimai.theme' => [
|
||||||
'active_warning' => 3,
|
'active_warning' => 3,
|
||||||
'box_color' => 'green',
|
'box_color' => 'green',
|
||||||
@@ -115,18 +114,17 @@ class AppExtensionTest extends TestCase
|
|||||||
'mini' => null,
|
'mini' => null,
|
||||||
'company' => null,
|
'company' => null,
|
||||||
'title' => null,
|
'title' => null,
|
||||||
|
'translation' => null,
|
||||||
],
|
],
|
||||||
'auto_reload_datatable' => false,
|
'auto_reload_datatable' => false,
|
||||||
'autocomplete_chars' => 3,
|
'autocomplete_chars' => 3,
|
||||||
],
|
],
|
||||||
'kimai.theme.select_type' => null,
|
'kimai.theme.select_type' => null,
|
||||||
'kimai.theme.show_about' => true,
|
'kimai.theme.show_about' => true,
|
||||||
|
|
||||||
'kimai.fosuser' => [
|
'kimai.fosuser' => [
|
||||||
'registration' => true,
|
'registration' => true,
|
||||||
'password_reset' => true,
|
'password_reset' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'kimai.timesheet' => [
|
'kimai.timesheet' => [
|
||||||
'mode' => 'default',
|
'mode' => 'default',
|
||||||
'markdown_content' => false,
|
'markdown_content' => false,
|
||||||
@@ -174,6 +172,7 @@ class AppExtensionTest extends TestCase
|
|||||||
'ROLE_ADMIN' => [],
|
'ROLE_ADMIN' => [],
|
||||||
'ROLE_SUPER_ADMIN' => [],
|
'ROLE_SUPER_ADMIN' => [],
|
||||||
],
|
],
|
||||||
|
'kimai.i18n_domains' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
// nasty parameter, should be removed!!!
|
// nasty parameter, should be removed!!!
|
||||||
@@ -322,6 +321,34 @@ class AppExtensionTest extends TestCase
|
|||||||
$this->assertEquals('(&(objectClass=inetOrgPerson))', $ldapConfig['user']['filter']);
|
$this->assertEquals('(&(objectClass=inetOrgPerson))', $ldapConfig['user']['filter']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testTranslationOverwritesEmpty()
|
||||||
|
{
|
||||||
|
$minConfig = $this->getMinConfig();
|
||||||
|
$this->extension->load($minConfig, $container = $this->getContainer());
|
||||||
|
|
||||||
|
$config = $container->getParameter('kimai.i18n_domains');
|
||||||
|
$this->assertEquals([], $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTranslationOverwrites()
|
||||||
|
{
|
||||||
|
$minConfig = $this->getMinConfig();
|
||||||
|
$minConfig['kimai']['industry'] = [
|
||||||
|
'translation' => 'xxxx',
|
||||||
|
];
|
||||||
|
$minConfig['kimai']['theme'] = [
|
||||||
|
'branding' => [
|
||||||
|
'translation' => 'yyyy',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->extension->load($minConfig, $container = $this->getContainer());
|
||||||
|
|
||||||
|
$config = $container->getParameter('kimai.i18n_domains');
|
||||||
|
// oder is important, theme/installation specific translations win
|
||||||
|
$this->assertEquals(['yyyy', 'xxxx'], $config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \PHPUnit\Framework\Error\Notice
|
* @expectedException \PHPUnit\Framework\Error\Notice
|
||||||
* @expectedExceptionMessage Found invalid "kimai" configuration: The child node "data_dir" at path "kimai" must be configured.
|
* @expectedExceptionMessage Found invalid "kimai" configuration: The child node "data_dir" at path "kimai" must be configured.
|
||||||
|
|||||||
@@ -219,4 +219,138 @@ class ConfigurationTest extends TestCase
|
|||||||
];
|
];
|
||||||
self::assertEquals($expected, $finalizedConfig['ldap']);
|
self::assertEquals($expected, $finalizedConfig['ldap']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFullDefaultConfig()
|
||||||
|
{
|
||||||
|
$fullDefaultConfig = [
|
||||||
|
'data_dir' => '/tmp/',
|
||||||
|
'plugin_dir' => '/tmp/',
|
||||||
|
'timesheet' => [
|
||||||
|
'default_begin' => 'now',
|
||||||
|
'mode' => 'default',
|
||||||
|
'markdown_content' => false,
|
||||||
|
'rounding' => [],
|
||||||
|
'rates' => [],
|
||||||
|
'active_entries' => [
|
||||||
|
'soft_limit' => 1,
|
||||||
|
'hard_limit' => 1,
|
||||||
|
],
|
||||||
|
'rules' => [
|
||||||
|
'allow_future_times' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'registration' => true,
|
||||||
|
'password_reset' => true,
|
||||||
|
],
|
||||||
|
'invoice' => [
|
||||||
|
'documents' => [
|
||||||
|
0 => 'var/invoices/',
|
||||||
|
1 => 'templates/invoice/renderer/',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'languages' => [],
|
||||||
|
'calendar' => [
|
||||||
|
'week_numbers' => true,
|
||||||
|
'day_limit' => 4,
|
||||||
|
'slot_duration' => '00:30:00',
|
||||||
|
'businessHours' => [
|
||||||
|
'days' => [
|
||||||
|
0 => 1,
|
||||||
|
1 => 2,
|
||||||
|
2 => 3,
|
||||||
|
3 => 4,
|
||||||
|
4 => 5,
|
||||||
|
],
|
||||||
|
'begin' => '08:00',
|
||||||
|
'end' => '20:00',
|
||||||
|
],
|
||||||
|
'visibleHours' => [
|
||||||
|
'begin' => '00:00',
|
||||||
|
'end' => '23:59',
|
||||||
|
],
|
||||||
|
'google' => [
|
||||||
|
'api_key' => null,
|
||||||
|
'sources' => [
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'weekends' => true,
|
||||||
|
],
|
||||||
|
'theme' => [
|
||||||
|
'active_warning' => 3,
|
||||||
|
'box_color' => 'green',
|
||||||
|
'select_type' => null,
|
||||||
|
'auto_reload_datatable' => false,
|
||||||
|
'show_about' => true,
|
||||||
|
'chart' => [
|
||||||
|
'background_color' => 'rgba(0,115,183,0.7)',
|
||||||
|
'border_color' => '#3b8bba',
|
||||||
|
'grid_color' => 'rgba(0,0,0,.05)',
|
||||||
|
'height' => '200',
|
||||||
|
],
|
||||||
|
'branding' => [
|
||||||
|
'logo' => null,
|
||||||
|
'mini' => null,
|
||||||
|
'company' => null,
|
||||||
|
'title' => null,
|
||||||
|
'translation' => null,
|
||||||
|
],
|
||||||
|
'autocomplete_chars' => 3,
|
||||||
|
],
|
||||||
|
'industry' => [
|
||||||
|
'translation' => null,
|
||||||
|
],
|
||||||
|
'dashboard' => [],
|
||||||
|
'widgets' => [],
|
||||||
|
'defaults' => [
|
||||||
|
'customer' => [
|
||||||
|
'timezone' => null,
|
||||||
|
'country' => 'DE',
|
||||||
|
'currency' => 'EUR',
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'timezone' => null,
|
||||||
|
'language' => 'en',
|
||||||
|
'theme' => null,
|
||||||
|
'currency' => 'EUR',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'permissions' => [
|
||||||
|
'sets' => [],
|
||||||
|
'maps' => [],
|
||||||
|
'roles' => [
|
||||||
|
'ROLE_USER' => [],
|
||||||
|
'ROLE_TEAMLEAD' => [],
|
||||||
|
'ROLE_ADMIN' => [],
|
||||||
|
'ROLE_SUPER_ADMIN' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ldap' => [
|
||||||
|
'connection' => [
|
||||||
|
'host' => null,
|
||||||
|
'port' => 389,
|
||||||
|
'useStartTls' => false,
|
||||||
|
'useSsl' => false,
|
||||||
|
'bindRequiresDn' => true,
|
||||||
|
'accountFilterFormat' => null,
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'baseDn' => null,
|
||||||
|
'filter' => '',
|
||||||
|
'attributesFilter' => '(objectClass=*)',
|
||||||
|
'usernameAttribute' => 'uid',
|
||||||
|
'attributes' => [],
|
||||||
|
],
|
||||||
|
'role' => [
|
||||||
|
'baseDn' => null,
|
||||||
|
'usernameAttribute' => 'dn',
|
||||||
|
'nameAttribute' => 'cn',
|
||||||
|
'userDnAttribute' => 'member',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertConfig($this->getMinConfig(), $fullDefaultConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class UserTest extends TestCase
|
|||||||
$user = new User();
|
$user = new User();
|
||||||
$this->assertInstanceOf(ArrayCollection::class, $user->getPreferences());
|
$this->assertInstanceOf(ArrayCollection::class, $user->getPreferences());
|
||||||
$this->assertNull($user->getTitle());
|
$this->assertNull($user->getTitle());
|
||||||
|
$this->assertNull($user->getDisplayName());
|
||||||
$this->assertNull($user->getAvatar());
|
$this->assertNull($user->getAvatar());
|
||||||
$this->assertNull($user->getAlias());
|
$this->assertNull($user->getAlias());
|
||||||
$this->assertNull($user->getId());
|
$this->assertNull($user->getId());
|
||||||
@@ -73,17 +74,20 @@ class UserTest extends TestCase
|
|||||||
$this->assertEquals('I like rain', $user->getPreferenceValue('test2'));
|
$this->assertEquals('I like rain', $user->getPreferenceValue('test2'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testToString()
|
public function testDisplayName()
|
||||||
{
|
{
|
||||||
$user = new User();
|
$user = new User();
|
||||||
|
|
||||||
$user->setUsername('bar');
|
$user->setUsername('bar');
|
||||||
$this->assertEquals('bar', (string) $user);
|
$this->assertEquals('bar', $user->getDisplayName());
|
||||||
$this->assertEquals('bar', $user->getUsername());
|
$this->assertEquals('bar', $user->getUsername());
|
||||||
|
$this->assertEquals('bar', (string) $user);
|
||||||
|
|
||||||
$user->setAlias('foo');
|
$user->setAlias('foo');
|
||||||
$this->assertEquals('foo', (string) $user);
|
|
||||||
$this->assertEquals('foo', $user->getAlias());
|
$this->assertEquals('foo', $user->getAlias());
|
||||||
|
$this->assertEquals('bar', $user->getUsername());
|
||||||
|
$this->assertEquals('foo', $user->getDisplayName());
|
||||||
|
$this->assertEquals('foo', (string) $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetLocale()
|
public function testGetLocale()
|
||||||
|
|||||||
@@ -190,7 +190,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="label.username">
|
<trans-unit id="label.username">
|
||||||
<source>label.username</source>
|
<source>label.username</source>
|
||||||
<target>Benutzername</target>
|
<target>Benutzer</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="label.description">
|
<trans-unit id="label.description">
|
||||||
<source>label.description</source>
|
<source>label.description</source>
|
||||||
|
|||||||
@@ -190,7 +190,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="label.username">
|
<trans-unit id="label.username">
|
||||||
<source>label.username</source>
|
<source>label.username</source>
|
||||||
<target>Username</target>
|
<target>User</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="label.description">
|
<trans-unit id="label.description">
|
||||||
<source>label.description</source>
|
<source>label.description</source>
|
||||||
|
|||||||
Reference in New Issue
Block a user