Skip to content

Commit 3816a3d

Browse files
committed
fix(translator): generate unique constants name (fix #938)
1 parent b1aba24 commit 3816a3d

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

src/Translator/src/TranslationsDumper.php

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\Translator;
1313

1414
use Symfony\Component\Filesystem\Filesystem;
15+
use Symfony\Component\String\Slugger\SluggerInterface;
1516
use Symfony\Component\Translation\MessageCatalogueInterface;
1617
use Symfony\UX\Translator\MessageParameters\Extractor\IntlMessageParametersExtractor;
1718
use Symfony\UX\Translator\MessageParameters\Extractor\MessageParametersExtractor;
@@ -33,27 +34,28 @@
3334
class TranslationsDumper
3435
{
3536
public function __construct(
36-
private string $dumpDir,
37-
private MessageParametersExtractor $messageParametersExtractor,
38-
private IntlMessageParametersExtractor $intlMessageParametersExtractor,
37+
private string $dumpDir,
38+
private MessageParametersExtractor $messageParametersExtractor,
39+
private IntlMessageParametersExtractor $intlMessageParametersExtractor,
3940
private TypeScriptMessageParametersPrinter $typeScriptMessageParametersPrinter,
40-
private Filesystem $filesystem,
41-
) {
41+
private Filesystem $filesystem,
42+
)
43+
{
4244
}
4345

4446
public function dump(MessageCatalogueInterface ...$catalogues): void
4547
{
4648
$this->filesystem->mkdir($this->dumpDir);
47-
$this->filesystem->remove($this->dumpDir.'/index.js');
48-
$this->filesystem->remove($this->dumpDir.'/index.d.ts');
49-
$this->filesystem->remove($this->dumpDir.'/configuration.js');
50-
$this->filesystem->remove($this->dumpDir.'/configuration.d.ts');
49+
$this->filesystem->remove($this->dumpDir . '/index.js');
50+
$this->filesystem->remove($this->dumpDir . '/index.d.ts');
51+
$this->filesystem->remove($this->dumpDir . '/configuration.js');
52+
$this->filesystem->remove($this->dumpDir . '/configuration.d.ts');
5153

5254
$translationsJs = '';
5355
$translationsTs = "import { Message, NoParametersType } from '@symfony/ux-translator';\n\n";
5456

5557
foreach ($this->getTranslations(...$catalogues) as $translationId => $translationsByDomainAndLocale) {
56-
$constantName = s($translationId)->ascii()->snake()->upper()->toString();
58+
$constantName = $this->generateConstantName($translationId);
5759

5860
$translationsJs .= sprintf(
5961
"export const %s = %s;\n",
@@ -70,13 +72,13 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
7072
);
7173
}
7274

73-
$this->filesystem->dumpFile($this->dumpDir.'/index.js', $translationsJs);
74-
$this->filesystem->dumpFile($this->dumpDir.'/index.d.ts', $translationsTs);
75-
$this->filesystem->dumpFile($this->dumpDir.'/configuration.js', sprintf(
75+
$this->filesystem->dumpFile($this->dumpDir . '/index.js', $translationsJs);
76+
$this->filesystem->dumpFile($this->dumpDir . '/index.d.ts', $translationsTs);
77+
$this->filesystem->dumpFile($this->dumpDir . '/configuration.js', sprintf(
7678
"export const localeFallbacks = %s;\n",
7779
json_encode($this->getLocaleFallbacks(...$catalogues), \JSON_THROW_ON_ERROR)
7880
));
79-
$this->filesystem->dumpFile($this->dumpDir.'/configuration.d.ts', <<<'TS'
81+
$this->filesystem->dumpFile($this->dumpDir . '/configuration.d.ts', <<<'TS'
8082
import { LocaleType } from '@symfony/ux-translator';
8183
8284
export declare const localeFallbacks: Record<LocaleType, LocaleType>;
@@ -95,8 +97,8 @@ private function getTranslations(MessageCatalogueInterface ...$catalogues): arra
9597
$locale = $catalogue->getLocale();
9698
foreach ($catalogue->getDomains() as $domain) {
9799
foreach ($catalogue->all($domain) as $id => $message) {
98-
$realDomain = $catalogue->has($id, $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)
99-
? $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX
100+
$realDomain = $catalogue->has($id, $domain . MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)
101+
? $domain . MessageCatalogueInterface::INTL_DOMAIN_SUFFIX
100102
: $domain;
101103

102104
$translations[$id] ??= [];
@@ -139,13 +141,13 @@ private function getTranslationsTypeScriptTypeDefinition(array $translationsByDo
139141
'Message<{ %s }, %s>',
140142
implode(', ', array_reduce(
141143
array_keys($parametersTypes),
142-
fn (array $carry, string $domain) => [
144+
fn(array $carry, string $domain) => [
143145
...$carry,
144146
sprintf("'%s': { parameters: %s }", $domain, $parametersTypes[$domain]),
145147
],
146148
[],
147149
)),
148-
implode('|', array_map(fn (string $locale) => "'$locale'", array_unique($locales))),
150+
implode('|', array_map(fn(string $locale) => "'$locale'", array_unique($locales))),
149151
);
150152
}
151153

@@ -159,4 +161,19 @@ private function getLocaleFallbacks(MessageCatalogueInterface ...$catalogues): a
159161

160162
return $localesFallbacks;
161163
}
164+
165+
private function generateConstantName(string $translationId): string
166+
{
167+
static $alreadyGenerated = [];
168+
169+
$prefix = 0;
170+
do {
171+
$constantName = s($translationId)->ascii()->snake()->upper()->toString().($prefix > 0 ? '_' . $prefix : '');
172+
$prefix += 1;
173+
} while (in_array($constantName, $alreadyGenerated, true));
174+
175+
$alreadyGenerated[] = $constantName;
176+
177+
return $constantName;
178+
}
162179
}

src/Translator/tests/TranslationsDumperTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPUnit\Framework\TestCase;
66
use Symfony\Component\Filesystem\Filesystem;
7+
use Symfony\Component\String\Slugger\AsciiSlugger;
78
use Symfony\Component\Translation\MessageCatalogue;
89
use Symfony\UX\Translator\MessageParameters\Extractor\IntlMessageParametersExtractor;
910
use Symfony\UX\Translator\MessageParameters\Extractor\MessageParametersExtractor;
@@ -40,10 +41,13 @@ public function testDump(): void
4041
'notification.comment_created' => 'Your post received a comment!',
4142
'notification.comment_created.description' => 'Your post "{title}" has received a new comment. You can read the comment by following <a href="{link}">this link</a>',
4243
'post.num_comments' => '{count, plural, one {# comment} other {# comments}}',
44+
'post.num_comments.' => '{count, plural, one {# comment} other {# comments}} (should not conflict with the previous one.)',
4345
],
4446
'messages' => [
4547
'symfony.great' => 'Symfony is awesome!',
4648
'symfony.what' => 'Symfony is %what%!',
49+
'symfony.what!' => 'Symfony is %what%! (should not conflict with the previous one.)',
50+
'symfony.what.' => 'Symfony is %what%. (should also not conflict with the previous one.)',
4751
'apples.count.0' => 'There is 1 apple|There are %count% apples',
4852
'apples.count.1' => '{1} There is one apple|]1,Inf] There are %count% apples',
4953
'apples.count.2' => '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
@@ -53,6 +57,8 @@ public function testDump(): void
5357
'what.count.2' => '{0} There are no %what%|{1} There is one %what%|]1,Inf] There are %count% %what%',
5458
'what.count.3' => 'one: There is one %what%|more: There are %count% %what%',
5559
'what.count.4' => 'one: There is one %what%|more: There are more than one %what%',
60+
'animal.dog-cat' => 'Dog and cat',
61+
'animal.dog_cat' => 'Dog and cat (should not conflict with the previous one)',
5662
],
5763
'foobar' => [
5864
'post.num_comments' => 'There is 1 comment|There are %count% comments',
@@ -63,10 +69,13 @@ public function testDump(): void
6369
'notification.comment_created' => 'Votre article a reçu un commentaire !',
6470
'notification.comment_created.description' => 'Votre article "{title}" a reçu un nouveau commentaire. Vous pouvez lire le commentaire en suivant <a href="{link}">ce lien</a>',
6571
'post.num_comments' => '{count, plural, one {# commentaire} other {# commentaires}}',
72+
'post.num_comments.' => '{count, plural, one {# commentaire} other {# commentaires}} (ne doit pas rentrer en conflit avec la traduction précédente)',
6673
],
6774
'messages' => [
6875
'symfony.great' => 'Symfony est génial !',
6976
'symfony.what' => 'Symfony est %what%!',
77+
'symfony.what!' => 'Symfony est %what%! (ne doit pas rentrer en conflit avec la traduction précédente)',
78+
'symfony.what.' => 'Symfony est %what%. (ne doit pas non plus rentrer en conflit avec la traduction précédente)',
7079
'apples.count.0' => 'Il y a 1 pomme|Il y a %count% pommes',
7180
'apples.count.1' => '{1} Il y a une pomme|]1,Inf] Il y a %count% pommes',
7281
'apples.count.2' => '{0} Il n\'y a pas de pommes|{1} Il y a une pomme|]1,Inf] Il y a %count% pommes',
@@ -76,6 +85,8 @@ public function testDump(): void
7685
'what.count.2' => '{0} Il n\'y a pas de %what%|{1} Il y a une %what%|]1,Inf] Il y a %count% %what%',
7786
'what.count.3' => 'one: Il y a une %what%|more: Il y a %count% %what%',
7887
'what.count.4' => 'one: Il y a une %what%|more: Il y a more than one %what%',
88+
'animal.dog-cat' => 'Chien et chat',
89+
'animal.dog_cat' => 'Chien et chat (ne doit pas rentrer en conflit avec la traduction précédente)',
7990
],
8091
'foobar' => [
8192
'post.num_comments' => 'Il y a 1 comment|Il y a %count% comments',
@@ -90,8 +101,11 @@ public function testDump(): void
90101
export const NOTIFICATION_COMMENT_CREATED = {"id":"notification.comment_created","translations":{"messages+intl-icu":{"en":"Your post received a comment!","fr":"Votre article a re\u00e7u un commentaire !"}}};
91102
export const NOTIFICATION_COMMENT_CREATED_DESCRIPTION = {"id":"notification.comment_created.description","translations":{"messages+intl-icu":{"en":"Your post \"{title}\" has received a new comment. You can read the comment by following <a href=\"{link}\">this link<\/a>","fr":"Votre article \"{title}\" a re\u00e7u un nouveau commentaire. Vous pouvez lire le commentaire en suivant <a href=\"{link}\">ce lien<\/a>"}}};
92103
export const POST_NUM_COMMENTS = {"id":"post.num_comments","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}}","fr":"{count, plural, one {# commentaire} other {# commentaires}}"},"foobar":{"en":"There is 1 comment|There are %count% comments","fr":"Il y a 1 comment|Il y a %count% comments"}}};
104+
export const POST_NUM_COMMENTS_1 = {"id":"post.num_comments.","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}} (should not conflict with the previous one.)","fr":"{count, plural, one {# commentaire} other {# commentaires}} (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
93105
export const SYMFONY_GREAT = {"id":"symfony.great","translations":{"messages":{"en":"Symfony is awesome!","fr":"Symfony est g\u00e9nial !"}}};
94106
export const SYMFONY_WHAT = {"id":"symfony.what","translations":{"messages":{"en":"Symfony is %what%!","fr":"Symfony est %what%!"}}};
107+
export const SYMFONY_WHAT_1 = {"id":"symfony.what!","translations":{"messages":{"en":"Symfony is %what%! (should not conflict with the previous one.)","fr":"Symfony est %what%! (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
108+
export const SYMFONY_WHAT_2 = {"id":"symfony.what.","translations":{"messages":{"en":"Symfony is %what%. (should also not conflict with the previous one.)","fr":"Symfony est %what%. (ne doit pas non plus rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
95109
export const APPLES_COUNT0 = {"id":"apples.count.0","translations":{"messages":{"en":"There is 1 apple|There are %count% apples","fr":"Il y a 1 pomme|Il y a %count% pommes"}}};
96110
export const APPLES_COUNT1 = {"id":"apples.count.1","translations":{"messages":{"en":"{1} There is one apple|]1,Inf] There are %count% apples","fr":"{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
97111
export const APPLES_COUNT2 = {"id":"apples.count.2","translations":{"messages":{"en":"{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples","fr":"{0} Il n'y a pas de pommes|{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
@@ -101,6 +115,8 @@ public function testDump(): void
101115
export const WHAT_COUNT2 = {"id":"what.count.2","translations":{"messages":{"en":"{0} There are no %what%|{1} There is one %what%|]1,Inf] There are %count% %what%","fr":"{0} Il n'y a pas de %what%|{1} Il y a une %what%|]1,Inf] Il y a %count% %what%"}}};
102116
export const WHAT_COUNT3 = {"id":"what.count.3","translations":{"messages":{"en":"one: There is one %what%|more: There are %count% %what%","fr":"one: Il y a une %what%|more: Il y a %count% %what%"}}};
103117
export const WHAT_COUNT4 = {"id":"what.count.4","translations":{"messages":{"en":"one: There is one %what%|more: There are more than one %what%","fr":"one: Il y a une %what%|more: Il y a more than one %what%"}}};
118+
export const ANIMAL_DOG_CAT = {"id":"animal.dog-cat","translations":{"messages":{"en":"Dog and cat","fr":"Chien et chat"}}};
119+
export const ANIMAL_DOG_CAT_1 = {"id":"animal.dog_cat","translations":{"messages":{"en":"Dog and cat (should not conflict with the previous one)","fr":"Chien et chat (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
104120

105121
JAVASCRIPT);
106122

@@ -110,8 +126,11 @@ public function testDump(): void
110126
export declare const NOTIFICATION_COMMENT_CREATED: Message<{ 'messages+intl-icu': { parameters: NoParametersType } }, 'en'|'fr'>;
111127
export declare const NOTIFICATION_COMMENT_CREATED_DESCRIPTION: Message<{ 'messages+intl-icu': { parameters: { 'title': string, 'link': string } } }, 'en'|'fr'>;
112128
export declare const POST_NUM_COMMENTS: Message<{ 'messages+intl-icu': { parameters: { 'count': number } }, 'foobar': { parameters: { '%count%': number } } }, 'en'|'fr'>;
129+
export declare const POST_NUM_COMMENTS_1: Message<{ 'messages+intl-icu': { parameters: { 'count': number } } }, 'en'|'fr'>;
113130
export declare const SYMFONY_GREAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
114131
export declare const SYMFONY_WHAT: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
132+
export declare const SYMFONY_WHAT_1: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
133+
export declare const SYMFONY_WHAT_2: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
115134
export declare const APPLES_COUNT0: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
116135
export declare const APPLES_COUNT1: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
117136
export declare const APPLES_COUNT2: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
@@ -121,6 +140,8 @@ public function testDump(): void
121140
export declare const WHAT_COUNT2: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
122141
export declare const WHAT_COUNT3: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
123142
export declare const WHAT_COUNT4: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
143+
export declare const ANIMAL_DOG_CAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
144+
export declare const ANIMAL_DOG_CAT_1: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
124145

125146
TYPESCRIPT);
126147
}

0 commit comments

Comments
 (0)