fix(languages): show error instead of silently no-oping when adding a duplicate locale
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

addLocale() always cleared the input, even when LocaleSyncService rejected
the code because it was already supported - same silent-failure shape as
the widgets JSON bug fixed earlier this session. Now checks locales()
first and shows an inline error, leaving the input untouched, instead of
clearing it like the add succeeded. Verified live: typing an existing
locale code and clicking Add now shows 'This language is already supported.'

Navigation-section was also audited (id generation, label locale-migration,
reorder swap, grouped-footer read-only fallback) - no defects found, it's
solid as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-17 18:31:01 +04:00
parent 3839e2e1f6
commit 2417a7795b
6 changed files with 20 additions and 2 deletions

View File

@@ -13,7 +13,11 @@
/> />
<app-button variant="primary" (click)="addLocale()">{{ 'builder.addLanguage' | translate }}</app-button> <app-button variant="primary" (click)="addLocale()">{{ 'builder.addLanguage' | translate }}</app-button>
</div> </div>
<small class="field-desc">{{ 'builder.newLanguageDesc' | translate }}</small> @if (addLocaleError(); as addError) {
<p class="editor-error">{{ addError }}</p>
} @else {
<small class="field-desc">{{ 'builder.newLanguageDesc' | translate }}</small>
}
<div class="stack-list"> <div class="stack-list">
@for (code of locales(); track code) { @for (code of locales(); track code) {

View File

@@ -37,15 +37,25 @@ export class ProjectEditorLanguagesSectionComponent {
updateNewLocale(value: string): void { updateNewLocale(value: string): void {
this.newLocale.set(value); this.newLocale.set(value);
if (this.addLocaleError()) {
this.addLocaleError.set(null);
}
} }
readonly addLocaleError = signal<string | null>(null);
addLocale(): void { addLocale(): void {
const code = this.newLocale().trim(); const code = this.newLocale().trim().toLowerCase();
if (!code) { if (!code) {
return; return;
} }
if (this.locales().includes(code)) {
this.addLocaleError.set(this.translate.t('builder.languageAlreadyAdded'));
return;
}
this.facade.addLocale(code); this.facade.addLocale(code);
this.newLocale.set(''); this.newLocale.set('');
this.addLocaleError.set(null);
} }
removeLocale(code: string): void { removeLocale(code: string): void {

View File

@@ -679,6 +679,7 @@ export const en: Translations = {
catalogNavTopCarousel: 'Top category carousel', catalogNavTopCarousel: 'Top category carousel',
catalogNavTopCarouselDesc: 'Horizontally scrollable category chips above the catalog grid.', catalogNavTopCarouselDesc: 'Horizontally scrollable category chips above the catalog grid.',
newLanguageDesc: 'ISO language code to add as a supported storefront language (e.g. de, fr).', newLanguageDesc: 'ISO language code to add as a supported storefront language (e.g. de, fr).',
languageAlreadyAdded: 'This language is already supported.',
languagesTabDesc: 'Languages available to shoppers. The default language is used as a fallback everywhere.', languagesTabDesc: 'Languages available to shoppers. The default language is used as a fallback everywhere.',
linkLabelDesc: 'Text shown for this navigation link.', linkLabelDesc: 'Text shown for this navigation link.',
linkUrlDesc: 'Path or URL this navigation link points to.', linkUrlDesc: 'Path or URL this navigation link points to.',

View File

@@ -679,6 +679,7 @@ export const hy: Translations = {
catalogNavTopCarousel: 'Վերևի կատեգորիաների կարուսել', catalogNavTopCarousel: 'Վերևի կատեգորիաների կարուսել',
catalogNavTopCarouselDesc: 'Հորիզոնական թերթվող կատեգորիաների chip-եր կատալոգի ցանցի վերևում։', catalogNavTopCarouselDesc: 'Հորիզոնական թերթվող կատեգորիաների chip-եր կատալոգի ցանցի վերևում։',
newLanguageDesc: 'ISO լեզվի կոդ՝ որպես աջակցվող լեզու ավելացնելու համար (օր․՝ de, fr)։', newLanguageDesc: 'ISO լեզվի կոդ՝ որպես աջակցվող լեզու ավելացնելու համար (օր․՝ de, fr)։',
languageAlreadyAdded: 'Այս լեզուն արդեն ավելացված է։',
languagesTabDesc: 'Գնորդներին հասանելի լեզուներ։ Լռելյայն լեզուն օգտագործվում է որպես պահուստային ամենուր։', languagesTabDesc: 'Գնորդներին հասանելի լեզուներ։ Լռելյայն լեզուն օգտագործվում է որպես պահուստային ամենուր։',
linkLabelDesc: 'Այս նավիգացիոն հղման համար ցուցադրվող տեքստը։', linkLabelDesc: 'Այս նավիգացիոն հղման համար ցուցադրվող տեքստը։',
linkUrlDesc: 'Ուղին կամ URL-ը, որին ուղղորդում է այս նավիգացիոն հղումը։', linkUrlDesc: 'Ուղին կամ URL-ը, որին ուղղորդում է այս նավիգացիոն հղումը։',

View File

@@ -679,6 +679,7 @@ export const ru: Translations = {
catalogNavTopCarousel: 'Карусель категорий сверху', catalogNavTopCarousel: 'Карусель категорий сверху',
catalogNavTopCarouselDesc: 'Горизонтально прокручиваемые чипы категорий над сеткой каталога.', catalogNavTopCarouselDesc: 'Горизонтально прокручиваемые чипы категорий над сеткой каталога.',
newLanguageDesc: 'Код языка ISO для добавления в поддерживаемые языки витрины (например, de, fr).', newLanguageDesc: 'Код языка ISO для добавления в поддерживаемые языки витрины (например, de, fr).',
languageAlreadyAdded: 'Этот язык уже добавлен.',
languagesTabDesc: 'Языки, доступные покупателям. Язык по умолчанию используется как резервный везде.', languagesTabDesc: 'Языки, доступные покупателям. Язык по умолчанию используется как резервный везде.',
linkLabelDesc: 'Текст, отображаемый для этой ссылки навигации.', linkLabelDesc: 'Текст, отображаемый для этой ссылки навигации.',
linkUrlDesc: 'Путь или URL, на который ведёт эта ссылка навигации.', linkUrlDesc: 'Путь или URL, на который ведёт эта ссылка навигации.',

View File

@@ -677,6 +677,7 @@ export interface Translations {
catalogNavTopCarousel: string; catalogNavTopCarousel: string;
catalogNavTopCarouselDesc: string; catalogNavTopCarouselDesc: string;
newLanguageDesc: string; newLanguageDesc: string;
languageAlreadyAdded: string;
languagesTabDesc: string; languagesTabDesc: string;
linkLabelDesc: string; linkLabelDesc: string;
linkUrlDesc: string; linkUrlDesc: string;