From b917e621cc25d387a395c6c83f8a783ec7f4b821 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Wed, 11 Oct 2023 17:05:44 +0200 Subject: [PATCH 01/49] fix(admin-ui): Make ExtensionHostComponent work with new extension APIs The new APIs require standalone components. --- .../components/extension-host/extension-host.component.ts | 3 +++ packages/admin-ui/src/lib/core/src/shared/shared.module.ts | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/admin-ui/src/lib/core/src/shared/components/extension-host/extension-host.component.ts b/packages/admin-ui/src/lib/core/src/shared/components/extension-host/extension-host.component.ts index f4f7e4ed74..ffabc06563 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/extension-host/extension-host.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/components/extension-host/extension-host.component.ts @@ -9,6 +9,7 @@ import { } from '@angular/core'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; +import { SharedModule } from '../../shared.module'; import { ExtensionHostConfig } from './extension-host-config'; import { ExtensionHostService } from './extension-host.service'; @@ -22,6 +23,8 @@ import { ExtensionHostService } from './extension-host.service'; templateUrl: './extension-host.component.html', styleUrls: ['./extension-host.component.scss'], changeDetection: ChangeDetectionStrategy.Default, + standalone: true, + imports: [SharedModule], providers: [ExtensionHostService], }) export class ExtensionHostComponent implements OnInit, AfterViewInit, OnDestroy { diff --git a/packages/admin-ui/src/lib/core/src/shared/shared.module.ts b/packages/admin-ui/src/lib/core/src/shared/shared.module.ts index 5f03fd28de..9ca91622c4 100644 --- a/packages/admin-ui/src/lib/core/src/shared/shared.module.ts +++ b/packages/admin-ui/src/lib/core/src/shared/shared.module.ts @@ -59,7 +59,6 @@ import { DropdownComponent } from './components/dropdown/dropdown.component'; import { EditNoteDialogComponent } from './components/edit-note-dialog/edit-note-dialog.component'; import { EmptyPlaceholderComponent } from './components/empty-placeholder/empty-placeholder.component'; import { EntityInfoComponent } from './components/entity-info/entity-info.component'; -import { ExtensionHostComponent } from './components/extension-host/extension-host.component'; import { FacetValueChipComponent } from './components/facet-value-chip/facet-value-chip.component'; import { FacetValueSelectorComponent } from './components/facet-value-selector/facet-value-selector.component'; import { FocalPointControlComponent } from './components/focal-point-control/focal-point-control.component'; @@ -249,7 +248,6 @@ const DECLARATIONS = [ ChannelAssignmentControlComponent, ChannelLabelPipe, IfDefaultChannelActiveDirective, - ExtensionHostComponent, CustomFieldLabelPipe, CustomFieldDescriptionPipe, FocalPointControlComponent, From 92cad43fdc005230a91013290ba6f6de58307840 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 12 Oct 2023 14:13:41 +0200 Subject: [PATCH 02/49] fix(core): Fix regression in ProductService.findOne not using relations Fixes #2443 --- packages/core/src/service/services/product.service.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/core/src/service/services/product.service.ts b/packages/core/src/service/services/product.service.ts index b5f2dbd98a..39ec1c63c3 100644 --- a/packages/core/src/service/services/product.service.ts +++ b/packages/core/src/service/services/product.service.ts @@ -120,12 +120,7 @@ export class ProductService { effectiveRelations.push('facetValues.facet'); } const product = await this.connection.findOneInChannel(ctx, Product, productId, ctx.channelId, { - // relations: unique(effectiveRelations), - relations: { - facetValues: { - facet: true, - }, - }, + relations: unique(effectiveRelations), where: { deletedAt: IsNull(), }, From eb548906dd0c180e0e74e0866ef39249ab647203 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 12 Oct 2023 14:29:47 +0200 Subject: [PATCH 03/49] docs: Add redirects for old docs urls --- docs/static/_redirects | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/static/_redirects diff --git a/docs/static/_redirects b/docs/static/_redirects new file mode 100644 index 0000000000..95e6949e57 --- /dev/null +++ b/docs/static/_redirects @@ -0,0 +1,22 @@ +# Add redirects for popular pages from old docs +/getting-started /guides/getting-started/installation +/developer-guide/customizing-models /guides/developer-guide/custom-fields +/developer-guide/authentication /guides/core-concepts/auth +/developer-guide/payment-integrations /reference/core-plugins/payments-plugin +/plugins /guides/developer-guide/plugins +/plugins/extending-the-admin-ui /guides/extending-the-admin-ui/getting-started +/storefront/building-a-storefront /guides/storefront/storefront-starters +/developer-guide/multi-vendor-marketplaces /guides/how-to/multi-vendor-marketplaces +/developer-guide/configuration /guides/developer-guide/configuration +/plugins/plugin-examples/extending-graphql-api /guides/developer-guide/extend-graphql-api +/storefront/order-workflow /guides/storefront/active-order +/typescript-api/custom-fields /reference/typescript-api/custom-fields +/developer-guide/migrations /guides/developer-guide/migrations +/typescript-api/core-plugins/payments-plugin/stripe-plugin /reference/core-plugins/payments-plugin/stripe-plugin +/developer-guide/channels /guides/core-concepts/channels +/graphql-api/shop /reference/graphql-api/shop/queries +/developer-guide/customizing-the-order-process /guides/core-concepts/orders +/developer-guide/importing-product-data /guides/developer-guide/importing-data +/plugins/plugin-examples /guides/developer-guide/plugins +/plugins/writing-a-vendure-plugin /guides/developer-guide/plugins +/deployment/using-docker /guides/deployment/using-docker From 9aac191e7e39b0501d0891b147b562a8206e972e Mon Sep 17 00:00:00 2001 From: Gautier Darchen Date: Thu, 12 Oct 2023 14:32:00 +0200 Subject: [PATCH 04/49] fix(common): Remove trademark symbols from normalized strings (#2447) --- packages/common/src/normalize-string.spec.ts | 2 +- packages/common/src/normalize-string.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/common/src/normalize-string.spec.ts b/packages/common/src/normalize-string.spec.ts index 27bfca888d..b0324c8ac1 100644 --- a/packages/common/src/normalize-string.spec.ts +++ b/packages/common/src/normalize-string.spec.ts @@ -25,7 +25,7 @@ describe('normalizeString()', () => { it('strips non-alphanumeric characters', () => { expect(normalizeString('hi!!!')).toBe('hi'); expect(normalizeString('who? me?')).toBe('who me'); - expect(normalizeString('!"£$%^&*()+[]{};:@#~?/,|\\><`¬\'=')).toBe(''); + expect(normalizeString('!"£$%^&*()+[]{};:@#~?/,|\\><`¬\'=©®™')).toBe(''); }); it('allows a subset of non-alphanumeric characters to pass through', () => { diff --git a/packages/common/src/normalize-string.ts b/packages/common/src/normalize-string.ts index 48aa45c676..b3fc74c814 100644 --- a/packages/common/src/normalize-string.ts +++ b/packages/common/src/normalize-string.ts @@ -8,6 +8,6 @@ export function normalizeString(input: string, spaceReplacer = ' '): string { .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() - .replace(/[!"£$%^&*()+[\]{};:@#~?\\/,|><`¬'=‘’]/g, '') + .replace(/[!"£$%^&*()+[\]{};:@#~?\\/,|><`¬'=‘’©®™]/g, '') .replace(/\s+/g, spaceReplacer); } From b594c55a9db4a1a1bc2ff0ef17e27da28fa8fd10 Mon Sep 17 00:00:00 2001 From: kristiand00 <63717005+kristiand00@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:33:33 +0200 Subject: [PATCH 05/49] feat(admin-ui): Add Croatian translation (#2442) --- .../src/lib/static/i18n-messages/hr.json | 795 ++++++++++++++++++ 1 file changed, 795 insertions(+) create mode 100644 packages/admin-ui/src/lib/static/i18n-messages/hr.json diff --git a/packages/admin-ui/src/lib/static/i18n-messages/hr.json b/packages/admin-ui/src/lib/static/i18n-messages/hr.json new file mode 100644 index 0000000000..9119278775 --- /dev/null +++ b/packages/admin-ui/src/lib/static/i18n-messages/hr.json @@ -0,0 +1,795 @@ +{ + "admin": { + "create-new-administrator": "Kreiraj novog administratora" + }, + "asset": { + "add-asset": "Dodaj medij", + "add-asset-with-count": "Dodaj {count, plural, =0 {medija} one {1 medij} few {{count} medija} other {{count} medija}}", + "assets-selected-count": "Odabrano {count} medija", + "dimensions": "Dimenzije", + "focal-point": "Fokalna točka", + "notify-create-assets-success": "Kreirano {count, plural, one {1 novo sredstvo} few {{count} nova sredstva} other {{count} novih sredstava}}", + "original-asset-size": "Izvorna veličina", + "preview": "Pregled", + "remove-asset": "Ukloni medij", + "select-asset": "Odaberi medij", + "select-assets": "Odaberi medije", + "set-as-featured-asset": "Postavi kao istaknuti medij", + "set-focal-point": "Postavi fokalnu točku", + "source-file": "Izvorna datoteka", + "unset-focal-point": "Poništi", + "update-focal-point": "Ažuriraj točku", + "update-focal-point-error": "Nije moguće ažurirati fokalnu točku", + "update-focal-point-success": "Fokalna točka ažurirana", + "upload-assets": "Prenesi medije", + "uploading": "Prenosim..." + }, + "breadcrumb": { + "administrators": "Administratori", + "assets": "Mediji", + "channels": "Kanali", + "collections": "Kolekcije", + "countries": "Zemlje", + "customer-groups": "Grupa kupaca", + "customers": "Kupci", + "dashboard": "Kontrolna ploča", + "facets": "Aspekti", + "global-settings": "Globalne postavke", + "job-queue": "Red poslova", + "manage-variants": "Upravljaj varijantama", + "modifying": "Uređivanje", + "orders": "Narudžbe", + "payment-methods": "Načini plaćanja", + "product-options": "Opcije proizvoda", + "products": "Artikli", + "profile": "Profil", + "promotions": "Promocije", + "roles": "Uloge", + "seller-orders": "Narudžbe prodavača", + "sellers": "Prodavači", + "shipping-methods": "Načini dostave", + "stock-locations": "Lokacije zaliha", + "system-status": "Status sustava", + "tax-categories": "Porezne kategorije", + "tax-rates": "Porezne stope", + "zones": "Zone" + }, + "catalog": { + "add-facet-value": "Dodaj vrijednost aspekta", + "add-facets": "Dodaj aspekte", + "add-option": "Dodaj opciju", + "add-price-in-another-currency": "Dodaj cijenu u drugoj valuti", + "add-stock-location": "Dodaj lokaciju zaliha", + "add-stock-to-location": "Dodaj zalihe na lokaciju", + "asset": "Sredstvo", + "asset-preview-links": "Poveznice za pregled sredstva", + "assets": "Sredstva", + "assign-product-to-channel-success": "Uspješno dodijeljeno {count, plural, one {1 proizvod} other {{count} proizvoda}} kanalu {channel}", + "assign-products-to-channel": "Dodijeli proizvode kanalu", + "assign-to-channel": "Dodijeli kanalu", + "assign-to-named-channel": "Dodijeli kanalu {channelCode}", + "assign-variant-to-channel-success": "Uspješno dodijeljeno {count, plural, one {1 varijanta proizvoda} other {{count} varijanti proizvoda}} kanalu {channel}", + "assign-variants-to-channel": "Dodijeli varijante proizvoda kanalu", + "auto-update-option-variant-name": "Automatski ažuriraj nazive varijanti proizvoda koristeći ovu opciju", + "auto-update-product-variant-name": "Automatski ažuriraj nazive varijanti proizvoda", + "cannot-create-variants-without-options": "Nije moguće stvoriti varijante proizvoda dok nije definirana grupa opcija s najmanje dvije opcije proizvoda", + "channel-price-preview": "Pregled cijene za kanal", + "collection": "Kolekcija", + "collection-contents": "Sadržaj kolekcije", + "collections": "Kolekcije", + "confirm-bulk-delete-products": "Izbrisati {count} proizvoda?", + "confirm-cancel": "Odustati?", + "confirm-delete-assets": "Izbrisati {count} {count, plural, one {sredstvo} other {sredstava}}?", + "confirm-delete-facet-value": "Izbrisati vrijednost aspekta?", + "confirm-delete-product": "Izbrisati proizvod?", + "confirm-delete-product-option": "Izbrisati opciju proizvoda \"{name}\"?", + "confirm-delete-product-option-group": "Izbrisati grupu opcija proizvoda \"{name}\"?", + "confirm-delete-product-option-group-body": "Ova grupa opcija se koristi za {count} {count, plural, one {varijantu} other {varijante}}. Jeste li sigurni da želite izbrisati?", + "confirm-delete-product-variant": "Izbrisati varijantu proizvoda \"{name}\"?", + "confirm-deletion-of-unused-variants-body": "Sljedeće varijante proizvoda su postale zastarjele zbog dodavanja novih opcija. Bit će izbrisane tijekom stvaranja novih varijanti proizvoda.", + "confirm-deletion-of-unused-variants-title": "", + "create-draft-order": "Kreiraj privremenu narudžbu", + "create-new-collection": "Kreiraj novu kolekciju", + "create-new-facet": "Kreiraj novi aspekt", + "create-new-product": "Novi proizvod", + "create-new-stock-location": "Kreiraj novu lokaciju zaliha", + "create-product-option-group": "Kreiraj grupu opcija proizvoda", + "create-product-variant": "Kreiraj varijantu proizvoda", + "default-currency": "Zadana valuta", + "do-not-inherit-filters": "Ne nasljeđuj filtre", + "drop-files-to-upload": "Ispustite datoteke za prijenos", + "edit-facet-values": "Uredi vrijednosti aspekta", + "edit-options": "Uredi opcije", + "facet": "Aspekt", + "facet-value-not-available": "Vrijednost aspekta \"{id}\" nije dostupna", + "facet-values": "Vrijednosti aspekta", + "facets": "Aspekti", + "filter-by-name": "Filtriraj po imenu", + "filter-by-sku": "Filtriraj po SKU", + "filter-inheritance": "Nasljeđivanje filtera", + "filters": "Filteri", + "inherit-filters-from-parent": "Naslijedi filtere od roditelja", + "live-preview-contents": "Sadržaj uživo", + "manage-variants": "Upravljanje varijantama", + "move-collection-to": "Premjesti u {name}", + "move-collections": "Premjesti kolekcije", + "move-collections-success": "Premješteno {count} kolekcija", + "move-down": "Premjesti dolje", + "move-to": "Premjesti na", + "move-up": "Premjesti gore", + "name": "Naziv", + "no-channel-selected": "Nije odabran kanal", + "no-featured-asset": "Nema istaknutih sredstava", + "no-selection": "Nema odabira", + "no-stock-locations-available-on-current-channel": "Nema dostupnih lokacija zaliha na trenutnom kanalu. Postavite barem jednu lokaciju zaliha prije dodavanja proizvoda.", + "notify-bulk-delete-products-success": "Uspješno izbrisano {count, plural, one {1 proizvod} other {{count} proizvoda}}", + "notify-remove-facets-from-channel-success": "Uspješno uklonjeno {count, plural, one {1 aspekt} other {{count} aspekta}} iz kanala {channelCode}", + "notify-remove-product-from-channel-error": "Nije moguće ukloniti proizvod iz kanala", + "notify-remove-product-from-channel-success": "Uspješno uklonjen proizvod iz kanala", + "notify-remove-variant-from-channel-error": "Nije moguće ukloniti varijantu proizvoda iz kanala", + "notify-remove-variant-from-channel-success": "Uspješno uklonjena varijanta proizvoda iz kanala", + "number-of-variants": "Broj varijanti", + "option": "Opcija", + "option-name": "Naziv opcije", + "option-values": "Vrijednosti opcije", + "out-of-stock-threshold": "Prag rasprodaje", + "out-of-stock-threshold-tooltip": "Postavlja razinu zaliha na kojoj se ova varijanta smatra rasprodanom. Upotreba negativne vrijednosti omogućuje podršku za narudžbe proizvoda koji trenutačno nisu dostupni.", + "page-description-options-editor": "Uredi nazive i kodove opcija za ovaj proizvod. Za dodavanje ili uklanjanje opcija koristite gumb \"Upravljanje varijantama\" ispod popisa varijanti proizvoda.", + "price": "Cijena", + "price-and-tax": "Cijena i porez", + "price-conversion-factor": "Faktor konverzije cijene", + "price-in-channel": "Cijena u { channel }", + "price-includes-tax-at": "Uključuje porez od { rate }%", + "price-with-tax-in-default-zone": "Uklj. { rate }% poreza: { price }", + "private": "Privatno", + "product": "Proizvod", + "product-name": "Naziv proizvoda", + "product-options": "Opcije proizvoda", + "product-variant-exists": "Varijanta proizvoda s ovim opcijama već postoji", + "product-variants": "Varijante proizvoda", + "products": "Proizvodi", + "public": "Javno", + "quick-jump-placeholder": "Brzo pretraživanje varijante", + "rebuild-search-index": "Obnovi indeks pretraživanja", + "reindex-error": "Došlo je do pogreške prilikom obnove indeksa pretraživanja", + "reindex-successful": "Indeksirano {count, plural, one {proizvodna varijanta} other {{count} proizvodnih varijanti}} u {time}ms", + "reindexing": "Obnova indeksa pretraživanja", + "remove-from-channel": "Ukloni iz {channelCode, select, undefined{kanala} other{{channelCode}}}", + "remove-option": "Ukloni opciju", + "remove-product-from-channel": "Ukloni proizvod iz kanala", + "remove-product-variant-from-channel": "Ukloni varijantu proizvoda iz kanala", + "reorder-collection": "Promijeni redoslijed kolekcije", + "root-collection": "Glavna kolekcija", + "run-pending-search-index-updates": "Pretraživanje: izvrši {count, plural, one {1 čekajuću nadogradnju} other {{count} čekajućih nadogradnji}}", + "running-search-index-updates": "Izvršava se {count, plural, one {1 nadogradnja} other {{count} nadogradnji}} indeksa pretraživanja", + "search-asset-name-or-tag": "Pretraži po nazivu sredstva ili oznakama", + "search-for-term": "Pretraži po terminu", + "search-product-name-or-code": "Pretraži po nazivu proizvoda ili kodu", + "select-product": "Odaberi proizvod", + "select-product-variant": "Odaberi varijantu proizvoda", + "sku": "SKU", + "slug": "Slug", + "slug-pattern-error": "Slug nije valjan", + "stock-allocated": "Dodijeljeno", + "stock-levels": "Razine zaliha", + "stock-location": "Lokacija zaliha", + "stock-locations": "Lokacije zaliha", + "stock-on-hand": "Na skladištu", + "tax-category": "Porezna kategorija", + "taxes": "Porezi", + "track-inventory": "Prati inventar", + "track-inventory-false": "Ne prati", + "track-inventory-inherit": "Naslijedi iz globalnih postavki", + "track-inventory-tooltip": "Kada se prati, razine zaliha varijanti proizvoda automatski će se prilagođavati prilikom prodaje", + "track-inventory-true": "Prati", + "update-product-option": "Ažuriraj opciju proizvoda", + "use-global-value": "Koristi globalnu vrijednost", + "values": "Vrijednosti", + "variant": "Varijanta", + "variant-count": "{count, plural, one {1 varijanta} other {{count} varijante}}", + "view-contents": "Pregledaj sadržaj", + "visibility": "Vidljivost" + }, + "common": { + "ID": "ID", + "add-filter": "Dodaj filter", + "add-item-to-list": "Dodaj stavku na popis", + "add-note": "Dodaj bilješku", + "apply": "Primijeni", + "assign-to-channel": "Dodijeli kanalu", + "available-currencies": "Dostupne valute", + "available-languages": "Dostupni jezici", + "boolean-and": "i", + "boolean-false": "netočno", + "boolean-or": "ili", + "boolean-true": "točno", + "breadcrumb": "Trag", + "browser-default": "Zadano za preglednik", + "cancel": "Odustani", + "cancel-navigation": "Prekini navigaciju", + "change-selection": "Promijeni odabir", + "channel": "Kanal", + "channels": "Kanali", + "clear-selection": "Očisti odabir", + "code": "Kod", + "collapse-entries": "Skupi unose", + "confirm": "Potvrdi", + "confirm-bulk-assign-to-channel": "Dodijeliti stavke kanalu?", + "confirm-bulk-delete": "Izbrisati odabrane stavke?", + "confirm-bulk-remove-from-channel": "Ukloniti stavke iz trenutnog kanala?", + "confirm-delete-note": "Izbrisati bilješku?", + "confirm-navigation": "Potvrdi navigaciju", + "contents": "Sadržaj", + "create": "Stvori", + "created-at": "Stvoreno u", + "custom-fields": "Prilagođena polja", + "data-table-filter-date-mode": "Mod datuma", + "data-table-filter-date-range": "Raspon datuma", + "data-table-filter-date-relative": "Relativan datum", + "default-channel": "Zadani kanal", + "default-language": "Zadani jezik", + "default-tax-category": "Zadana porezna kategorija", + "delete": "Izbriši", + "description": "Opis", + "details": "Detalji", + "disabled": "Onemogućeno", + "discard-changes": "Odbaci promjene", + "edit": "Uredi", + "edit-field": "Uredi polje", + "edit-note": "Uredi bilješku", + "enabled": "Omogućeno", + "end-date": "Datum završetka", + "expand-entries": "Proširi unose", + "extension-running-in-separate-window": "Proširenje radi u zasebnom prozoru", + "filter": "Filter", + "filter-preset-name": "Filter preset name", + "force-delete": "Snažno izbriši", + "force-remove": "Snažno ukloni", + "general": "Općenito", + "guest": "Gost", + "id": "ID", + "image": "Slika", + "items-per-page-option": "{ count } po stranici", + "items-selected-count": "{ count } {count, plural, one {stavka} other {stavke}} odabrano", + "keep-editing": "Nastavi uređivanje", + "language": "Jezik", + "launch-extension": "Pokreni proširenje", + "list-items-and-n-more": "{ items } i {nMore} više", + "live-update": "Stvarno vrijeme ažuriranje", + "locale": "Lokalno", + "log-out": "Odjava", + "login": "Prijava", + "login-image-title": "Bok! Dobrodošli natrag. Drago nam je vidjeti vas.", + "login-title": "Prijavi se na {brand}", + "manage-tags": "Upravljanje oznakama", + "manage-tags-description": "Ažurirajte ili izbrišite oznake globalno.", + "medium-date": "Srednji datum", + "more": "Više...", + "name": "Ime", + "no-alerts": "Nema upozorenja", + "no-bulk-actions-available": "Nema dostupnih grupnih radnji", + "no-results": "Nema rezultata", + "not-applicable": "Nije primjenjivo", + "not-set": "Nije postavljeno", + "notify-assign-to-channel-success-with-count": "Uspješno dodijeljeno {count, plural, one {1 stavka} other {{count} stavki}} kanalu { channelCode }", + "notify-bulk-update-success": "Ažurirano { count } { entity }", + "notify-create-error": "Došlo je do pogreške, nije moguće stvoriti { entity }", + "notify-create-success": "Stvoreno novo { entity }", + "notify-delete-error": "Došlo je do pogreške, nije moguće izbrisati { entity }", + "notify-delete-error-with-count": "Nije moguće izbrisati {count, plural, one {1 stavku} other {{count} stavki}}", + "notify-delete-success": "Izbrisano { entity }", + "notify-delete-success-with-count": "Uspješno izbrisano {count, plural, one {1 stavka} other {{count} stavki}}", + "notify-remove-from-channel-success-with-count": "Uspješno uklonjeno { count } stavki iz kanala", + "notify-save-changes-error": "Došlo je do pogreške, nije moguće spremiti promjene", + "notify-saved-changes": "Spremljene promjene", + "notify-update-error": "Došlo je do pogreške, nije moguće ažurirati { entity }", + "notify-update-success": "Ažurirano { entity }", + "notify-updated-tags-success": "Uspješno ažurirane oznake", + "okay": "U redu", + "operator-contains": "sadrži", + "operator-eq": "jednako", + "operator-gt": "veće od", + "operator-lt": "manje od", + "operator-not-contains": "ne sadrži", + "operator-not-eq": "nije jednako", + "operator-notContains": "ne sadrži", + "operator-regex": "odgovara regexu", + "password": "Lozinka", + "position": "Pozicija", + "price": "Cijena", + "price-with-tax": "Cijena s porezom", + "private": "Privatno", + "public": "Javno", + "remember-me": "Zapamti me", + "remove": "Ukloni", + "remove-from-channel": "Ukloni iz trenutnog kanala", + "remove-item-from-list": "Ukloni stavku s popisa", + "rename-filter-preset": "Rename preset", + "reset-columns": "Ponovno postavi stupce", + "results-count": "{ count } {count, plural, one {rezultat} other {rezultata}}", + "sample-formatting": "Primjer formatiranja", + "save-filter-preset": "Spremi kao prečac", + "search-and-filter-list": "Pretraži i filtriraj ovaj popis", + "search-by-name": "Pretraži po imenu", + "select": "Odaberi...", + "select-display-language": "Odaberi jezik prikaza", + "select-items-with-count": "Odaberi { count } {count, plural, one {stavku} other {stavki}}", + "select-products": "Odaberi proizvode", + "select-relation-id": "Odaberi ID odnosa", + "select-table-columns": "Odaberi stupce tablice", + "select-today": "Odaberi danas", + "select-variants": "Odaberi varijante", + "seller": "Prodavač", + "set-language": "Postavi jezik", + "short-date": "Kratki datum", + "slug": "Slug", + "start-date": "Datum početka", + "status": "Status", + "tags": "Oznake", + "theme": "Tema", + "there-are-unsaved-changes": "Imate nespremljenih promjena. Napuštanje će rezultirati gubitkom tih promjena.", + "toggle-all": "Uključi/Isključi sve", + "total-items": "{currentStart} - {currentEnd} od {totalItems}", + "update": "Ažuriraj", + "updated-at": "Ažurirano u", + "username": "Korisničko ime", + "value": "Vrijednost", + "view-contents": "Pogledaj sadržaj", + "view-next-month": "Pogledaj sljedeći mjesec", + "view-previous-month": "Pogledaj prethodni mjesec", + "visibility": "Vidljivost", + "with-selected": "S odabranima ({count})..." + }, + "customer": { + "add-customer-to-group": "Dodaj kupca u grupu", + "add-customer-to-groups-with-count": "Dodaj kupca u {count, plural, one {1 grupu} other {{count} grupe}}", + "add-customers-to-group": "Dodaj kupce u grupu", + "add-customers-to-group-success": "Dodano {customerCount, plural, one {1 kupac} other {{customerCount} kupaca}} u \"{ groupName }\"", + "add-customers-to-group-with-count": "Dodaj {count, plural, one {1 kupca} other {{count} kupaca}}", + "add-customers-to-group-with-name": "Dodaj kupce u \"{ groupName }\"", + "addresses": "Adrese", + "city": "Grad", + "company": "Tvrtka", + "confirm-remove-customer-from-group": "Ukloniti kupca iz grupe?", + "country": "Država", + "create-customer-group": "Stvori grupu kupaca", + "create-new-address": "Stvori novu adresu", + "create-new-customer": "Stvori novog kupca", + "create-new-customer-group": "Stvori novu grupu kupaca", + "customer": "Kupac", + "customer-group": "Grupa kupaca", + "customer-groups": "Grupe kupaca", + "customer-history": "Povijest kupca", + "customers": "Kupci", + "default-billing-address": "Zadana adresa za naplatu", + "default-shipping-address": "Zadana adresa za dostavu", + "email-address": "Adresa e-pošte", + "email-verification-sent": "Na { emailAddress } poslana je verifikacijska e-pošta", + "first-name": "Ime", + "full-name": "Puno ime", + "guest": "Gost", + "history-customer-added-to-group": "Kupac dodan u grupu \"{ groupName }\"", + "history-customer-address-created": "Adresa stvorena", + "history-customer-address-deleted": "Adresa izbrisana", + "history-customer-address-updated": "Adresa ažurirana", + "history-customer-detail-updated": "Ažurirani detalji kupca", + "history-customer-email-update-requested": "Zahtjev za ažuriranje adrese e-pošte", + "history-customer-email-update-verified": "Verifikacija ažurirane adrese e-pošte", + "history-customer-password-reset-requested": "Zahtjev za resetiranje lozinke", + "history-customer-password-reset-verified": "Verifikacija resetirane lozinke", + "history-customer-password-updated": "Ažurirana lozinka", + "history-customer-registered": "Kupac registriran", + "history-customer-removed-from-group": "Kupac uklonjen iz grupe \"{ groupName }\"", + "history-customer-verified": "Kupac verificiran", + "history-using-external-auth-strategy": "koristi { strategy }", + "history-using-native-auth-strategy": "koristi adresu e-pošte", + "last-login": "Posljednja prijava", + "last-name": "Prezime", + "name": "Ime", + "new-email-address": "Nova adresa e-pošte", + "no-orders-placed": "Nema postavljenih narudžbi", + "not-a-member-of-any-groups": "Ovaj kupac nije član nijedne grupe", + "old-email-address": "Stara adresa e-pošte", + "orders": "Narudžbe", + "password": "Lozinka", + "phone-number": "Broj telefona", + "postal-code": "Poštanski broj", + "province": "Županija", + "registered": "Registriran", + "remove-customers-from-group-success": "Uklonjeno {customerCount, plural, one {1 kupac} other {{customerCount} kupaca}} iz \"{ groupName }\"", + "remove-from-group": "Ukloni iz ove grupe", + "search-customers-by-email": "Pretraži po adresi e-pošte", + "search-customers-by-email-last-name-postal-code": "Pretraži po adresi e-pošte / prezimenu / poštanskom broju", + "select-customer": "Odaberi kupca", + "set-as-default-billing-address": "Postavi kao zadanu adresu za naplatu", + "set-as-default-shipping-address": "Postavi kao zadanu adresu za dostavu", + "street-line-1": "Adresa, redak 1", + "street-line-2": "Adresa, redak 2", + "title": "Naslov", + "update-customer-group": "Ažuriraj grupu kupaca", + "verified": "Verificirano", + "view-group-members": "Pogledaj članove grupe" + }, + "dashboard": { + "add-widget": "Dodaj widget", + "latest-orders": "Najnovije narudžbe", + "metric-average-order-value": "Prosječna vrijednost narudžbe", + "metric-number-of-orders": "Broj narudžbi", + "metric-order-total-value": "Ukupna vrijednost narudžbe", + "metrics": "Statistika", + "orders-summary": "Sažetak narudžbi", + "remove-widget": "Ukloni widget", + "thisMonth": "Ovaj mjesec", + "thisWeek": "Ovaj tjedan", + "today": "Danas", + "total-order-value": "Ukupna vrijednost", + "total-orders": "Ukupno narudžbi", + "widget-resize": "Promijeni veličinu", + "widget-width": "Širina: {width}", + "yesterday": "Jučer" + }, + "datetime": { + "ago-days": "{count, plural, one {prije 1 dan} other {prije {count} dana}}", + "ago-hours": "{count, plural, one {prije 1 sat} other {prije {count} sati}}", + "ago-minutes": "{count, plural, one {prije 1 minutu} other {prije {count} minuta}}", + "ago-seconds": "{count, plural, =0 {upravo sada} one {prije 1 sekunde} other {prije {count} sekundi}}", + "ago-years": "{count, plural, one {prije 1 godinu} other {prije {count} godina}}", + "day": "dan", + "duration-milliseconds": "{ms}ms", + "duration-minutes:seconds": "{m}:{s}m", + "duration-seconds": "{s}s", + "month": "mjesec", + "month-apr": "Travanj", + "month-aug": "Kolovoz", + "month-dec": "Prosinac", + "month-feb": "Veljača", + "month-jan": "Siječanj", + "month-jul": "Srpanj", + "month-jun": "Lipanj", + "month-mar": "Ožujak", + "month-may": "Svibanj", + "month-nov": "Studeni", + "month-oct": "Listopad", + "month-sep": "Rujan", + "relative-past-days": "Prošao {count, plural, one {1 dan} other {{count} dana}}", + "relative-past-months": "Prošao {count, plural, one {1 mjesec} other {{count} mjeseci}}", + "relative-past-years": "Prošlo {count, plural, one {1 godina} other {{count} godina}}", + "time": "Vrijeme", + "weekday-fr": "Pet", + "weekday-mo": "Pon", + "weekday-sa": "Sub", + "weekday-su": "Ned", + "weekday-th": "Čet", + "weekday-tu": "Uto", + "weekday-we": "Sri", + "year": "godina" + }, + "editor": { + "image-alt": "Opis (alt)", + "image-src": "Izvor", + "image-title": "Naziv", + "insert-image": "Umetni sliku", + "link-href": "Link", + "link-target": "Target linka", + "link-title": "Naziv linka", + "remove-link": "Ukloni", + "set-link": "Postavi link" + }, + "error": { + "403-forbidden": "Trenutno nemate ovlaštenje za pristup \"{ path }\". Ili vam nedostaju ovlasti ili je vaša sesija istekla.", + "could-not-connect-to-server": "Nije moguće povezati se s Vendure serverom na { url }", + "facet-value-form-values-do-not-match": "Broj vrijednosti u obrascu za aspekte ne podudara se s stvarnim brojem vrijednosti", + "health-check-failed": "Provjera zdravlja sustava nije uspjela", + "no-default-shipping-zone-set": "Ovaj kanal nema zadaniu zonu dostave. To može uzrokovati pogreške pri izračunavanju troškova dostave narudžbe.", + "no-default-tax-zone-set": "Ovaj kanal nema zadaniu poreznu zonu, što će uzrokovati pogreške pri izračunavanju cijena. Molimo kreirajte ili odaberite zonu." + }, + "marketing": { + "actions": "Radnje", + "add-action": "Dodaj radnju", + "add-condition": "Dodaj uvjet", + "conditions": "Uvjeti", + "coupon-code": "Kod kupona", + "create-new-promotion": "Stvori novu promociju", + "ends-at": "Završava u", + "per-customer-limit": "Limit po kupcu", + "per-customer-limit-tooltip": "Maksimalni broj puta koje ovu promociju može koristiti jedan pojedinačni kupac", + "promotion": "Promocija", + "search-by-name-or-coupon-code": "Pretraži po imenu ili kodu kupona", + "starts-at": "Počinje u", + "usage-limit": "Ukupno ograničenje upotrebe", + "usage-limit-tooltip": "Maksimalni broj puta koje se ovu promociju može koristiti ukupno" + }, + "nav": { + "administrators": "Administratori", + "assets": "Resursi", + "catalog": "Katalog", + "channels": "Kanali", + "collections": "Kolekcije", + "countries": "Države", + "customer-groups": "Grupe kupaca", + "customers": "Kupci", + "facets": "Aspekti", + "global-settings": "Globalne postavke", + "job-queue": "Red čekanja poslova", + "marketing": "Oglašavanje", + "orders": "Narudžbe", + "payment-methods": "Načini plaćanja", + "products": "Proizvodi", + "promotions": "Promocije", + "roles": "Uloge", + "sales": "Prodaja", + "sellers": "Prodavači", + "settings": "Postavke", + "shipping-methods": "Načini dostave", + "stock-locations": "Lokacije zaliha", + "system": "Sustav", + "system-status": "Status sustava", + "tax-categories": "Porezne kategorije", + "tax-rates": "Porezne stope", + "zones": "Zone" + }, + "order": { + "add-item-to-order": "Dodaj stavku u narudžbu", + "add-note": "Dodaj napomenu", + "add-payment": "Dodaj plaćanje", + "add-payment-to-order": "Dodaj plaćanje narudžbi", + "add-payment-to-order-success": "Uspješno dodano plaćanje narudžbi", + "add-surcharge": "Dodaj nadoplatu", + "added-items": "Dodane stavke", + "amount": "Iznos", + "arrange-additional-payment": "Organiziraj dodatno plaćanje", + "billing-address": "Adresa za naplatu", + "cancel": "Otkaži", + "cancel-entire-order": "Otkaži cijelu narudžbu", + "cancel-fulfillment": "Otkaži ispunjenje", + "cancel-modification": "Otkaži izmjenu", + "cancel-order": "Otkaži narudžbu ili stavke", + "cancel-payment": "Otkaži plaćanje", + "cancel-reason-customer-request": "Zahtjev kupca", + "cancel-reason-not-available": "Nedostupno", + "cancel-selected-items": "Otkaži odabrane stavke", + "cancel-specified-items": "Otkaži određene stavke", + "cancellation-reason": "Razlog otkazivanja", + "cancelled-order-success": "Narudžba uspješno otkazana", + "complete-draft-order": "Završi skicu", + "confirm-modifications": "Potvrdi izmjene", + "contents": "Sadržaj", + "create-fulfillment": "Stvori ispunjenje", + "create-fulfillment-success": "Ispunjenje stvoreno", + "customer": "Kupac", + "delete-draft-order": "Izbriši skicu", + "draft-order": "Skica narudžbe", + "edit-billing-address": "Uredi adresu za naplatu", + "edit-shipping-address": "Uredi adresu za dostavu", + "error-message": "Poruka o grešci", + "existing-address": "Postojeća adresa", + "existing-customer": "Postojeći kupac", + "filter-is-active": "Aktivan je", + "fulfill": "Ispuni", + "fulfill-order": "Ispuni narudžbu", + "fulfillment": "Ispunjenje", + "fulfillment-method": "Metoda ispunjenja", + "history-coupon-code-applied": "Primijenjen kod kupona", + "history-coupon-code-removed": "Uklonjen kod kupona", + "history-fulfillment-created": "Ispunjenje stvoreno", + "history-fulfillment-delivered": "Ispunjenje isporučeno", + "history-fulfillment-shipped": "Ispunjenje poslano", + "history-fulfillment-transition": "Ispunjenje prešlo iz {from} u {to}", + "history-items-cancelled": "{count} {count, plural, one {stavka} other {stavke}} otkazane", + "history-order-cancelled": "Narudžba otkazana", + "history-order-created": "Narudžba stvorena", + "history-order-fulfilled": "Narudžba ispunjena", + "history-order-modified": "Narudžba izmijenjena", + "history-order-transition": "Narudžba prešla iz {from} u {to}", + "history-payment-settled": "Plaćanje riješeno", + "history-payment-transition": "Plaćanje #{id} prešlo iz {from} u {to}", + "history-refund-transition": "Povrat #{id} prešao iz {from} u {to}", + "item-count": "{count} {count, plural, one {stavka} other {stavke}}", + "line-fulfillment-all": "Sve stavke su ispunjene", + "line-fulfillment-none": "Nijedna stavka nije ispunjena", + "line-fulfillment-partial": "{ count } od { total } stavki ispunjeno", + "manually-transition-to-state": "Ručno prijeđite u stanje...", + "manually-transition-to-state-message": "Ručno prijeđite narudžbu u drugo stanje. Napomena da su stanja narudžbe regulirana pravilima koja mogu spriječiti određene prijelaze.", + "modification-adding-items": "Dodavanje {count} {count, plural, one {stavka} other {stavke}}", + "modification-adding-surcharges": "Dodavanje {count} {count, plural, one {nadoplata} other {nadoplata}}", + "modification-adjusting-lines": "Prilagođavanje {count} {count, plural, one {linija} other {linija}}", + "modification-not-settled": "Nije riješeno", + "modification-recalculate-shipping": "Ponovno izračunaj dostavu", + "modification-settled": "Riješeno", + "modification-summary": "Sažetak izmjena", + "modification-updating-billing-address": "Ažuriranje adrese za naplatu", + "modification-updating-shipping-address": "Ažuriranje adrese za dostavu", + "modifications": "Izmjene", + "modify-order": "Izmijeni narudžbu", + "modify-order-price-difference": "Razlika u cijeni", + "net-price": "Neto cijena", + "note": "Napomena", + "note-is-private": "Napomena je privatna", + "note-only-visible-to-administrators": "Vidljivo samo administratorima", + "note-visible-to-customer": "Vidljivo administratorima i kupcu", + "order": "Narudžba", + "order-history": "Povijest narudžbe", + "order-is-empty": "Narudžba je prazna", + "order-state-diagram": "Dijagram stanja narudžbe", + "order-type": "Vrsta narudžbe", + "order-type-aggregate": "Agregat", + "order-type-regular": "Redovita", + "order-type-seller": "Prodavač", + "orders": "Narudžbe", + "original-quantity-at-checkout": "Izvorna količina pri završetku kupovine", + "payment": "Plaćanje", + "payment-amount": "Iznos plaćanja", + "payment-metadata": "Metapodaci plaćanja", + "payment-method": "Metoda plaćanja", + "payment-state": "Stanje plaćanja", + "payment-to-refund": "Plaćanje za povrat", + "payments": "Plaćanja", + "placed-at": "Naručeno u", + "preview-changes": "Pregled promjena", + "product-name": "Naziv proizvoda", + "product-sku": "SKU", + "promotions-applied": "Primijenjeni promotivni kodovi", + "prorated-unit-price": "Proračunska cijena po komadu", + "quantity": "Količina", + "refund": "Povrat sredstava", + "refund-adjustment": "Prilagodba", + "refund-and-cancel-order": "Povrat sredstava i otkazivanje narudžbe", + "refund-cancellation-reason": "Razlog povrata sredstava/otkazivanja", + "refund-cancellation-reason-required": "Razlog povrata sredstava/otkazivanja je obavezan", + "refund-metadata": "Metapodaci povrata sredstava", + "refund-order-failed": "Povrat narudžbe nije uspio", + "refund-order-success": "Uspješno izvršen povrat narudžbe", + "refund-reason": "Razlog povrata sredstava", + "refund-reason-customer-request": "Zahtjev kupca", + "refund-reason-not-available": "Nedostupno", + "refund-shipping": "Povrat troškova dostave", + "refund-total": "Ukupan iznos povrata", + "refund-total-error": "Ukupan iznos povrata mora biti između {min} i {max}", + "refund-total-warning": "Ukupan iznos povrata premašuje odabrani iznos plaćanja. Preostali iznos povrata bit će vraćen iz drugih plaćanja.", + "refund-with-amount": "Povrat u iznosu od {amount}", + "refunded-count": "Povraćeno {count} {count, plural, one {stavka} other {stavki}}", + "removed-items": "Uklonjene stavke", + "search-by-order-filters": "Pretraga po imenu / kodu / ID transakcije", + "select-address": "Odaberi adresu", + "select-shipping-method": "Odaberi metodu dostave", + "select-state": "Odaberi državu", + "seller-orders": "Narudžbe prodavača", + "set-billing-address": "Postavi adresu za naplatu", + "set-coupon-codes": "Postavi kôdove kupona", + "set-customer-for-order": "Postavi kupca", + "set-fulfillment-state": "Označi kao {state}", + "set-shipping-address": "Postavi adresu za dostavu", + "set-shipping-method": "Postavi metodu dostave", + "settle-payment": "Izmiri plaćanje", + "settle-payment-error": "Plaćanje nije moglo biti izmireno", + "settle-payment-success": "Plaćanje je uspješno izmireno", + "settle-refund": "Izmiri povrat sredstava", + "settle-refund-manual-instructions": "Nakon ručnog povrata putem vašeg pružatelja plaćanja ({method}), unesite ID transakcije ovdje.", + "settle-refund-success": "Povrat sredstava je uspješno izmiren", + "shipping": "Dostava", + "shipping-address": "Adresa dostave", + "shipping-cancelled": "Dostava otkazana", + "shipping-method": "Metoda dostave", + "state": "Stanje", + "sub-total": "Prije poreza", + "successfully-updated-fulfillment": "Dostava je uspješno ažurirana", + "surcharges": "Dodatne naknade", + "tax-base": "Osnova za porez", + "tax-description": "Opis poreza", + "tax-rate": "Porezna stopa", + "tax-summary": "Porezni sažetak", + "tax-total": "Ukupan porez", + "total": "Ukupno", + "tracking-code": "Kôd za praćenje", + "transaction-id": "ID transakcije", + "transition-to-state": "Prijelaz u stanje {state}", + "transitioned-payment-to-state-success": "Uspješno izvršen prijelaz plaćanja u stanje {state}", + "transitioned-to-state-success": "Uspješno izvršen prijelaz u stanje {state}", + "unable-to-transition-to-state-try-another": "Narudžba se nije mogla vratiti u stanje \"{state}\". Molimo odaberite alternativno stanje", + "unfulfilled": "Neispunjeno", + "unit-price": "Jedinična cijena" + }, + "settings": { + "add-countries-to-zone": "Dodaj države u { zoneName }", + "add-countries-to-zone-success": "Dodane su { countryCount } {countryCount, plural, one {država} other {države}} u zonu \"{ zoneName }\"", + "add-products-to-test-order": "Dodajte proizvode u test narudžbu", + "administrator": "Administrator", + "channel": "Kanal", + "channel-token": "Token kanala", + "country": "Država", + "create-new-channel": "Kreirajte novi kanal", + "create-new-country": "Kreirajte novu državu", + "create-new-payment-method": "Kreirajte novu metodu plaćanja", + "create-new-role": "Kreirajte novu ulogu", + "create-new-seller": "Kreirajte novog prodavača", + "create-new-shipping-method": "Kreirajte novu metodu dostave", + "create-new-tax-category": "Kreirajte novu kategoriju poreza", + "create-new-tax-rate": "Kreirajte novu stopu poreza", + "create-new-zone": "Kreirajte novu zonu", + "default-currency": "Zadana valuta", + "default-role-label": "Ovo je zadana uloga i ne može se mijenjati", + "default-shipping-zone": "Zadana zona dostave", + "default-tax-zone": "Zadana porezna zona", + "defaults": "Zadano", + "eligible": "Opravdan", + "email-address": "Adresa e-pošte", + "email-address-or-identifier": "Adresa e-pošte ili identifikator", + "first-name": "Ime", + "fulfillment-handler": "Izvršitelj", + "global-available-languages-tooltip": "Postavlja jezike koji su dostupni za sve kanale. Individualni kanali mogu podržavati podskup ovih jezika.", + "global-out-of-stock-threshold": "Globalni prag isteka zaliha", + "global-out-of-stock-threshold-tooltip": "Postavlja razinu zaliha na kojoj se varijanta smatra nedostupnom. Upotreba negativne vrijednosti omogućuje podršku za narudžbe izvan zaliha. Može se zamijeniti varijantama proizvoda.", + "last-name": "Prezime", + "no-eligible-shipping-methods": "Nema prikladnih metoda dostave", + "password": "Lozinka", + "payment-eligibility-checker": "Provjeravač opravdanosti plaćanja", + "payment-handler": "Rukovatelj plaćanjem", + "payment-method": "Metoda plaćanja", + "permissions": "Dozvole", + "prices-include-tax": "Cijene uključuju porez za zadanu zonu", + "profile": "Profil", + "rate": "Ocijeni", + "remove-countries-from-zone-success": "Uklonjeno { countryCount } {countryCount, plural, one {država} other {država}} iz zone \"{ zoneName }\"", + "remove-from-zone": "Uklonite iz zone", + "role": "Uloga", + "roles": "Uloge", + "search-by-product-name-or-sku": "Pretraži po nazivu proizvoda ili SKU", + "seller": "Prodavač", + "shipping-calculator": "Kalkulator dostave", + "shipping-eligibility-checker": "Provjera mogućnosti dostave", + "shipping-method": "Metoda dostave", + "tax-category": "Kategorija poreza", + "tax-rate": "Stopa poreza", + "test-address": "Testna adresa", + "test-result": "Rezultat testa", + "test-shipping-method": "Testna metoda dostave", + "test-shipping-methods": "Testne metode dostave", + "track-inventory-default": "Pratite zadani inventar", + "view-zone-members": "Pregledajte članove", + "zone": "Zona" + }, + "state": { + "adding-items": "Dodavanje stavki", + "arranging-additional-payment": "Organizacija dodatnog plaćanja", + "arranging-payment": "Organizacija plaćanja", + "authorized": "Odobreno", + "cancelled": "Otkazano", + "created": "Kreirano", + "declined": "Odbijeno", + "delivered": "Isporučeno", + "draft": "Skica", + "error": "Greška", + "failed": "Neuspelo", + "modifying": "Izmene", + "partially-delivered": "Delimično isporučeno", + "partially-shipped": "Delimično otpremljeno", + "payment-authorized": "Odobreno plaćanje", + "payment-settled": "Plaćanje izmireno", + "pending": "U tijeku", + "settled": "Izmireno", + "shipped": "Otpremljeno" + }, + "system": { + "all-job-queues": "Sve redove zadataka", + "health-all-systems-up": "Svi sistemi su dostupni", + "health-error": "Greška: jedan ili više sistema je nedostupno!", + "health-last-checked": "Poslednja provera", + "health-message": "Poruka", + "health-refresh": "Osvježi", + "health-status": "Status", + "health-status-down": "Nedostupno", + "health-status-up": "Dostupno", + "job-data": "Podaci o zadatku", + "job-duration": "Trajanje", + "job-error": "Greška zadatka", + "job-queue-name": "Ime reda zadatka", + "job-result": "Rezultat zadatka", + "job-state": "Stanje zadatka", + "job-state-all": "Svi statusi", + "job-state-cancelled": "Otkazano", + "job-state-completed": "Završeno", + "job-state-failed": "Neuspjelo", + "job-state-pending": "Na čekanju", + "job-state-running": "U tijeku" + } +} \ No newline at end of file From 3fd93c77f8476b1c672032dfb265eca4408a5e3f Mon Sep 17 00:00:00 2001 From: Alessio Marano <46132382+Swampy469@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:35:07 +0200 Subject: [PATCH 06/49] fix(admin-ui): Improve Italian translations (#2445) Co-authored-by: Alessio Marano --- .../src/lib/static/i18n-messages/it.json | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/admin-ui/src/lib/static/i18n-messages/it.json b/packages/admin-ui/src/lib/static/i18n-messages/it.json index 7608f60e5e..46e3fc4f0d 100644 --- a/packages/admin-ui/src/lib/static/i18n-messages/it.json +++ b/packages/admin-ui/src/lib/static/i18n-messages/it.json @@ -45,10 +45,10 @@ "profile": "Profilo", "promotions": "Promozioni", "roles": "Ruoli", - "seller-orders": "Ordini venditore", - "sellers": "Venditore", + "seller-orders": "Ordini venditori", + "sellers": "Venditori", "shipping-methods": "Metodi di spedizione", - "stock-locations": "Posizioni magazzino", + "stock-locations": "Posizioni scorte", "system-status": "Stato del sistema", "tax-categories": "Impostazioni Tasse", "tax-rates": "Importo Tasse", @@ -59,7 +59,7 @@ "add-facets": "Aggiungi attributi", "add-option": "Aggiungi opzione", "add-price-in-another-currency": "Aggiungi un prezzo in un'altra valuta", - "add-stock-location": "Aggiungi posizione magazzino", + "add-stock-location": "Aggiungi posizione scorte", "add-stock-to-location": "ggiungi merce alla posizione", "asset": "Media", "asset-preview-links": "Media preview link", @@ -92,8 +92,8 @@ "create-new-collection": "Crea nuova Collezione", "create-new-facet": "Crea nuovo attributo", "create-new-product": "Crea nuovo prodotto", - "create-new-stock-location": "Creare una nuova posizione di magazzino", - "create-product-option-group": "Crea gruppo di opzione di prodotto", + "create-new-stock-location": "Creare una nuova posizione delle scorte ", + "create-product-option-group": "Crea gruppo di opzione del prodotto", "create-product-variant": "Crea variante prodotto", "default-currency": "Valuta predefinita", "do-not-inherit-filters": "Non ereditare i filtri", @@ -121,7 +121,7 @@ "no-channel-selected": "Nessun canale selezionato", "no-featured-asset": "Nessun media in evidenza", "no-selection": "Nessuna selezione", - "no-stock-locations-available-on-current-channel": "Non sono disponibili posizioni di magazzino nel canale corrente. Impostare almeno una posizione di magazzino prima di aggiungere prodotti.", + "no-stock-locations-available-on-current-channel": "Non sono disponibili posizioni delle scorte nel canale corrente. Impostare almeno una posizione delle scorte prima di aggiungere prodotti.", "notify-bulk-delete-products-success": "Rimossi con successo {count, plural, one {1 prodotto} other {{count} prodotti}}", "notify-remove-facets-from-channel-success": "Rimossi con successo {count, plural, one {1 attributo} other {{count} attributi}} da { channelCode }", "notify-remove-product-from-channel-error": "Impossibile rimuovere il prodotto dal canale", @@ -159,7 +159,7 @@ "remove-product-from-channel": "Rimuovi prodotto dal canale", "remove-product-variant-from-channel": "Rimuovi variante dal canale", "reorder-collection": "Riordina collezione", - "root-collection": "", + "root-collection": "Collezione base", "run-pending-search-index-updates": "Indice di ricerca: eseguire {count, plural, one {1 aggiornamento in attesa} other {{count} aggiornamenti in attesa}}", "running-search-index-updates": "Esecuzione {count, plural, one {1 aggiornamento} other {{count} aggiornamenti}} all'indice di ricerca", "search-asset-name-or-tag": "Cerca per nome o tag del media", @@ -171,7 +171,7 @@ "slug": "Slug", "slug-pattern-error": "Lo slug non è valido", "stock-allocated": "Allocato", - "stock-levels": "Livelli di scorte", + "stock-levels": "Livello delle scorte", "stock-location": "Posizione delle scorte", "stock-locations": "Posizioni delle scorte", "stock-on-hand": "Scorte", @@ -203,7 +203,7 @@ "boolean-false": "falso", "boolean-or": "o", "boolean-true": "vero", - "breadcrumb": "Breadcrump", + "breadcrumb": "Breadcrumb", "browser-default": "Predefinito del browser", "cancel": "Annulla", "cancel-navigation": "Annulla navigazione", @@ -221,7 +221,7 @@ "confirm-navigation": "Prosegui con la navigazione", "contents": "Contenuti", "create": "Crea", - "created-at": "Creato il", + "created-at": "Creata il", "custom-fields": "Campi personalizzati", "data-table-filter-date-mode": "Modalità data", "data-table-filter-date-range": "Intervallo date", @@ -331,7 +331,7 @@ "toggle-all": "Cambia tutti", "total-items": "{currentStart} - {currentEnd} di {totalItems}", "update": "Aggiorna", - "updated-at": "Aggiornare a", + "updated-at": "Aggiornata il", "username": "Username", "value": "Valore", "view-contents": "Mostra contenuti", @@ -521,7 +521,7 @@ "sellers": "Venditori", "settings": "Impostazioni", "shipping-methods": "Metodi di Spedizione", - "stock-locations": "Posizioni magazzino", + "stock-locations": "Posizioni scorte", "system": "Sistema", "system-status": "Stato del Sistema", "tax-categories": "Impostazioni Tasse", @@ -581,7 +581,7 @@ "history-order-fulfilled": "Ordine spedito", "history-order-modified": "Ordine modificato", "history-order-transition": "Ordine passato da {from} a {to}", - "history-payment-settled": "Pagamento incassato", + "history-payment-settled": "Pagamento ricevuto", "history-payment-transition": "Pagamento #{id} passato da {from} a {to}", "history-refund-transition": "Rimborso #{id} passato da {from} a {to}", "item-count": "{count} {count, plural, one {prodotto} other {prodotti}}", @@ -593,12 +593,12 @@ "modification-adding-items": "Aggiungo {count} {count, plural, one {prodotto} other {prodotti}}", "modification-adding-surcharges": "Aggiungo {count} {count, plural, one {sovrapprezzo} other {sovrapprezzi}}", "modification-adjusting-lines": "Modifico {count} {count, plural, one {linea} other {linee}} dell'ordine", - "modification-not-settled": "Non incassato", + "modification-not-settled": "Non ricevuto", "modification-recalculate-shipping": "Ricalcola spedizione", - "modification-settled": "Incassato", + "modification-settled": "Ricevuto", "modification-summary": "Riepilogo delle modifiche", - "modification-updating-billing-address": "Aggiorno indirizzo di fatturazione", - "modification-updating-shipping-address": "Aggiorno indirizzo di spedizione", + "modification-updating-billing-address": "Aggiornando l'indirizzo di fatturazione", + "modification-updating-shipping-address": "Aggiornando l'indirizzo di spedizione", "modifications": "Modifiche", "modify-order": "Modifica ordine", "modify-order-price-difference": "Differenza di prezzo", @@ -692,7 +692,7 @@ "settings": { "add-countries-to-zone": "Aggiungi nazioni a { zoneName }", "add-countries-to-zone-success": "Ho aggiunto { countryCount } {countryCount, plural, one {nazione} other {nazioni}} alla zona \"{ zoneName }\"", - "add-products-to-test-order": "Aggiunti prodotti all'ordine di test", + "add-products-to-test-order": "Aggiunti prodotti all'ordine di prova", "administrator": "Amministratore", "channel": "Canale", "channel-token": "Token del canale", @@ -722,8 +722,8 @@ "last-name": "Cognome", "no-eligible-shipping-methods": "Nessun metodo di spedizione compatibile", "password": "Password", - "payment-eligibility-checker": "Metodo di verifica compatibilità pagamento", - "payment-handler": "Funzione gestione pagamento", + "payment-eligibility-checker": "Criterio di idoneità metodo di pagamento", + "payment-handler": "Gestore Pagamento", "payment-method": "Metodo di pagamento", "permissions": "Permessi", "prices-include-tax": "I prezzi includono le tasse per la zona fiscale predefinita", @@ -733,24 +733,24 @@ "remove-from-zone": "Rimuovi dalla zona", "role": "Ruolo", "roles": "Ruoli", - "search-by-product-name-or-sku": "Cerca per prodotto o codice SKU", + "search-by-product-name-or-sku": "Cerca per nome prodotto o codice SKU", "seller": "Venditore", - "shipping-calculator": "Calcolo prezzo spedizioni", - "shipping-eligibility-checker": "Criterio di selezione metodi di spedizione", + "shipping-calculator": "Calcolo prezzo spedizione", + "shipping-eligibility-checker": "Criterio di idoneità metodo di spedizione", "shipping-method": "Metodo di spedizione", "tax-category": "Categoria tasse", "tax-rate": "Tasso fiscale", - "test-address": "Indirizzo di test", - "test-result": "Risultato test", - "test-shipping-method": "Testa metodo di spedizione", - "test-shipping-methods": "Testa metodi di spedizione", + "test-address": "Indirizzo di prova", + "test-result": "Risultato prova", + "test-shipping-method": "Prova metodo di spedizione", + "test-shipping-methods": "Prova metodi di spedizione", "track-inventory-default": "Monitora inventario automaticamente", "view-zone-members": "Vedi membri", "zone": "Zona" }, "state": { "adding-items": "Aggiunta prodotti", - "arranging-additional-payment": "Scelta pagamento ulteriore", + "arranging-additional-payment": "Scelta ulteriore pagamento ", "arranging-payment": "Scelta pagamento", "authorized": "Autorizzato", "cancelled": "Cancellato", @@ -770,8 +770,8 @@ "shipped": "Spedito" }, "system": { - "all-job-queues": "Tutte le code di operazioni", - "health-all-systems-up": "Tutto il sistema è attivo", + "all-job-queues": "Tutte le operazioni", + "health-all-systems-up": "I sistemi sono attivi", "health-error": "Errore: uno o più sistemi sono inattivi!", "health-last-checked": "Ultimo controllo", "health-message": "Messaggio", @@ -792,4 +792,4 @@ "job-state-pending": "In attesa", "job-state-running": "In esecuzione" } -} \ No newline at end of file +} From 8db459a84338331d829a4750ebc14351574bf5fe Mon Sep 17 00:00:00 2001 From: Martijn Date: Thu, 12 Oct 2023 14:37:16 +0200 Subject: [PATCH 07/49] fix(payments-plugin): Fix Mollie klarna AutoCapture (#2446) --- packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts | 7 ++++--- packages/payments-plugin/src/mollie/mollie.service.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts index 0cab90543a..87c6bced31 100644 --- a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts +++ b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts @@ -381,8 +381,7 @@ describe('Mollie payments (with useDynamicRedirectUrl set to true)', () => { body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), headers: { 'Content-Type': 'application/json' }, }); - // tslint:disable-next-line:no-non-null-assertion - const { order: adminOrder } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order!.id }); + const { order: adminOrder } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order?.id }); expect(adminOrder.state).toBe('ArrangingPayment'); }); @@ -457,7 +456,7 @@ describe('Mollie payments (with useDynamicRedirectUrl set to true)', () => { adminClient, order.lines[0].id, 10, - // tslint:disable-next-line:no-non-null-assertion + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion order.payments!.find(p => p.amount === 108990)!.id, SURCHARGE_AMOUNT, ); @@ -468,6 +467,8 @@ describe('Mollie payments (with useDynamicRedirectUrl set to true)', () => { }); describe('Handle pay-later methods', () => { + // TODO: Add testcases that mock incoming webhook to: 1. Authorize payment and 2. AutoCapture payments + it('Should prepare a new order', async () => { await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); const { addItemToOrder } = await shopClient.query< diff --git a/packages/payments-plugin/src/mollie/mollie.service.ts b/packages/payments-plugin/src/mollie/mollie.service.ts index e48b7e2051..27e1f90498 100644 --- a/packages/payments-plugin/src/mollie/mollie.service.ts +++ b/packages/payments-plugin/src/mollie/mollie.service.ts @@ -308,6 +308,7 @@ export class MollieService { * Settle an existing payment based on the given mollieOrder */ async settleExistingPayment(ctx: RequestContext, order: Order, mollieOrderId: string): Promise { + order = await this.entityHydrator.hydrate(ctx, order, { relations: ['payments'] }); const payment = order.payments.find(p => p.transactionId === mollieOrderId); if (!payment) { throw Error( @@ -335,7 +336,7 @@ export class MollieService { } const client = createMollieClient({ apiKey }); // We use the orders API, so list available methods for that API usage - const methods = await client.methods.list({resource: 'orders'}); + const methods = await client.methods.list({ resource: 'orders' }); return methods.map(m => ({ ...m, code: m.id, From 3942690b8ebf80fdefa7a7d5c9f6208cdaeeb730 Mon Sep 17 00:00:00 2001 From: "(void *)0x0" <55084653+BijanRegmi@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:14:05 +0545 Subject: [PATCH 08/49] docs: Fix yarn add command (#2448) --- .../docs/guides/extending-the-admin-ui/getting-started/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/extending-the-admin-ui/getting-started/index.md b/docs/docs/guides/extending-the-admin-ui/getting-started/index.md index ee58610e14..6974a33fa2 100644 --- a/docs/docs/guides/extending-the-admin-ui/getting-started/index.md +++ b/docs/docs/guides/extending-the-admin-ui/getting-started/index.md @@ -31,7 +31,7 @@ npm install --save-dev @vendure/ui-devkit ```bash -yarn add --save-dev @vendure/ui-devkit +yarn add --dev @vendure/ui-devkit ``` From 09dd7df5e345d90b5bf7d171eabcbb103bc07543 Mon Sep 17 00:00:00 2001 From: armitjs Date: Tue, 17 Oct 2023 07:27:15 -0500 Subject: [PATCH 09/49] fix(core): Fix custom field resolver for eager translatable relation (#2457) Fixes #2453 --- .../api/common/custom-field-relation-resolver.service.ts | 8 ++++++++ packages/core/src/api/config/generate-resolvers.ts | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/core/src/api/common/custom-field-relation-resolver.service.ts b/packages/core/src/api/common/custom-field-relation-resolver.service.ts index 27ebba13ec..c008731fb6 100644 --- a/packages/core/src/api/common/custom-field-relation-resolver.service.ts +++ b/packages/core/src/api/common/custom-field-relation-resolver.service.ts @@ -55,6 +55,14 @@ export class CustomFieldRelationResolverService { const result = fieldDef.list ? await qb.getMany() : await qb.getOne(); + return await this.translateEntity(ctx, result, fieldDef); + } + + async translateEntity( + ctx: RequestContext, + result: VendureEntity | VendureEntity[] | null, + fieldDef: RelationCustomFieldConfig, + ) { if (fieldDef.entity === ProductVariant) { if (Array.isArray(result)) { await Promise.all(result.map(r => this.applyVariantPrices(ctx, r as any))); diff --git a/packages/core/src/api/config/generate-resolvers.ts b/packages/core/src/api/config/generate-resolvers.ts index 065c747294..bfde3f677c 100644 --- a/packages/core/src/api/config/generate-resolvers.ts +++ b/packages/core/src/api/config/generate-resolvers.ts @@ -207,10 +207,12 @@ function generateCustomFieldRelationResolvers( args: any, context: any, ) => { - if (source[fieldDef.name] != null) { - return source[fieldDef.name]; - } const ctx: RequestContext = context.req[REQUEST_CONTEXT_KEY]; + const eagerEntity = source[fieldDef.name]; + // If the relation is eager-loaded, we can simply try to translate this relation entity if they have translations + if (eagerEntity != null) { + return customFieldRelationResolverService.translateEntity(ctx, eagerEntity, fieldDef); + } const entityId = source[ENTITY_ID_KEY]; return customFieldRelationResolverService.resolveRelation({ ctx, From 83c8a33226e7ad9826fa8619ca24d2911353177d Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 17 Oct 2023 14:29:01 +0200 Subject: [PATCH 10/49] test(core): Add e2e test for #2453 --- .../e2e/custom-field-relations.e2e-spec.ts | 50 +++++- .../test-plugins/issue-2453/api/index.ts | 21 +++ .../entities/campaign-translation.entity.ts | 27 +++ .../issue-2453/entities/campaign.entity.ts | 33 ++++ .../custom-fields-collection.entity.ts | 51 ++++++ .../issue-2453/plugin-issue2453.ts | 30 ++++ .../issue-2453/services/campaign.service.ts | 161 ++++++++++++++++++ .../default-campaigns/default-campaigns.ts | 23 +++ .../services/default-campaigns/index.ts | 3 + 9 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/api/index.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign-translation.entity.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign.entity.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/entities/custom-fields-collection.entity.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/plugin-issue2453.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/services/campaign.service.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/default-campaigns.ts create mode 100644 packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/index.ts diff --git a/packages/core/e2e/custom-field-relations.e2e-spec.ts b/packages/core/e2e/custom-field-relations.e2e-spec.ts index c92b8ca3e6..8057315516 100644 --- a/packages/core/e2e/custom-field-relations.e2e-spec.ts +++ b/packages/core/e2e/custom-field-relations.e2e-spec.ts @@ -34,6 +34,7 @@ import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-conf import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods'; import { TestPlugin1636_1664 } from './fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin'; +import { PluginIssue2453 } from './fixtures/test-plugins/issue-2453/plugin-issue2453'; import { TestCustomEntity, WithCustomEntity } from './fixtures/test-plugins/with-custom-entity'; import { AddItemToOrderMutationVariables } from './graphql/generated-e2e-shop-types'; import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions'; @@ -115,7 +116,7 @@ const customConfig = mergeConfig(testConfig(), { timezone: 'Z', }, customFields: customFieldConfig, - plugins: [TestPlugin1636_1664, WithCustomEntity], + plugins: [TestPlugin1636_1664, WithCustomEntity, PluginIssue2453], }); describe('Custom field relations', () => { @@ -286,6 +287,53 @@ describe('Custom field relations', () => { assertTranslatableCustomFieldValues(product); }); + // https://github.com/vendure-ecommerce/vendure/issues/2453 + it('translatable eager-loaded relation works (issue 2453)', async () => { + const { collections } = await adminClient.query(gql` + query { + collections(options: { sort: { name: DESC } }) { + items { + id + name + customFields { + campaign { + name + languageCode + } + } + } + } + } + `); + + expect(collections.items).toEqual([ + { + customFields: { + campaign: null, + }, + id: 'T_2', + name: 'parent collection', + }, + { + customFields: { + campaign: { + languageCode: 'en', + name: 'Clearance Up to 70% Off frames', + }, + }, + id: 'T_3', + name: 'children collection', + }, + { + customFields: { + campaign: null, + }, + id: 'T_4', + name: 'Plants', + }, + ]); + }); + it('ProductVariant prices get resolved', async () => { const { product } = await adminClient.query(gql` query { diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/api/index.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/api/index.ts new file mode 100644 index 0000000000..f01f20e29a --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/api/index.ts @@ -0,0 +1,21 @@ +import { gql } from 'graphql-tag'; + +export const apiExtensions = gql` + type Campaign implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String + code: String! + promotion: Promotion + promotionId: ID + languageCode: LanguageCode! + translations: [CampaignTranslation!]! + } + + type CampaignTranslation implements Node { + id: ID! + languageCode: LanguageCode! + name: String! + } +`; diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign-translation.entity.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign-translation.entity.ts new file mode 100644 index 0000000000..eafbdc8104 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign-translation.entity.ts @@ -0,0 +1,27 @@ +import type { Translation } from '@vendure/core'; +import { DeepPartial, LanguageCode, VendureEntity } from '@vendure/core'; +import { Column, Entity, Relation, ManyToOne } from 'typeorm'; + +import { Campaign } from './campaign.entity'; + +/** + * @description This entity represents a front end campaign + * + * @docsCategory entities + */ +@Entity('campaign_translation') +export class CampaignTranslation extends VendureEntity implements Translation { + constructor(input?: DeepPartial>) { + super(input); + } + @Column('varchar') + languageCode: LanguageCode; + + @Column('varchar') + name: string; + + @ManyToOne(() => Campaign, base => base.translations, { + onDelete: 'CASCADE', + }) + base: Relation; +} diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign.entity.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign.entity.ts new file mode 100644 index 0000000000..deaf263c17 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign.entity.ts @@ -0,0 +1,33 @@ +import type { ID, LocaleString, Translation } from '@vendure/core'; +import { DeepPartial, Promotion, VendureEntity } from '@vendure/core'; +import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; + +import { CampaignTranslation } from './campaign-translation.entity'; + +/** + * @description This entity represents a front end campaign + * + * @docsCategory entities + */ +@Entity('campaign') +export class Campaign extends VendureEntity { + constructor(input?: DeepPartial) { + super(input); + } + + @Column({ unique: true }) + code: string; + + name: LocaleString; + + @ManyToOne(() => Promotion, { onDelete: 'SET NULL' }) + promotion: Promotion | null; + + @Column('int', { nullable: true }) + promotionId: ID | null; + + @OneToMany(() => CampaignTranslation, translation => translation.base, { + eager: true, + }) + translations: Array>; +} diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/custom-fields-collection.entity.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/custom-fields-collection.entity.ts new file mode 100644 index 0000000000..9fb9a86400 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/entities/custom-fields-collection.entity.ts @@ -0,0 +1,51 @@ +import type { CustomFieldConfig } from '@vendure/core'; +import { LanguageCode } from '@vendure/core'; + +import { Campaign } from './campaign.entity.js'; + +/** + * `Collection` basic custom fields for `campaign` + */ +export const collectionCustomFields: CustomFieldConfig[] = [ + { + type: 'relation', + name: 'campaign', + nullable: true, + entity: Campaign, + eager: true, + // 当shop-api必须定义schema `Campaign` type,申明, 因为public=true + public: true, + graphQLType: 'Campaign', + label: [ + { + languageCode: LanguageCode.en, + value: 'Campaign', + }, + ], + description: [ + { + languageCode: LanguageCode.en, + value: 'Campaign of this collection page', + }, + ], + }, + { + name: 'invisible', + type: 'boolean', + public: true, + nullable: true, + defaultValue: false, + label: [ + { + languageCode: LanguageCode.en, + value: 'Invisible', + }, + ], + description: [ + { + languageCode: LanguageCode.en, + value: 'This flag indicates if current collection is visible or inVisible, against `public`', + }, + ], + }, +]; diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/plugin-issue2453.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/plugin-issue2453.ts new file mode 100644 index 0000000000..7edca04861 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/plugin-issue2453.ts @@ -0,0 +1,30 @@ +import { PluginCommonModule, VendurePlugin } from '@vendure/core'; + +import { apiExtensions } from './api/index'; +import { CampaignTranslation } from './entities/campaign-translation.entity'; +import { Campaign } from './entities/campaign.entity'; +import { collectionCustomFields } from './entities/custom-fields-collection.entity'; +import { CampaignService } from './services/campaign.service'; + +@VendurePlugin({ + imports: [PluginCommonModule], + entities: [Campaign, CampaignTranslation], + adminApiExtensions: { + schema: apiExtensions, + }, + shopApiExtensions: { + schema: apiExtensions, + }, + compatibility: '>=2.0.0', + providers: [CampaignService], + configuration: config => { + config.customFields.Collection.push(...collectionCustomFields); + return config; + }, +}) +export class PluginIssue2453 { + constructor(private campaignService: CampaignService) {} + async onApplicationBootstrap() { + await this.campaignService.initCampaigns(); + } +} diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/services/campaign.service.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/campaign.service.ts new file mode 100644 index 0000000000..bba6ab1b20 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/campaign.service.ts @@ -0,0 +1,161 @@ +import { Injectable } from '@nestjs/common'; +import { DeletionResponse, DeletionResult } from '@vendure/common/lib/generated-types'; +import type { ID, ListQueryOptions, PaginatedList, Translated } from '@vendure/core'; +import { + assertFound, + CollectionService, + LanguageCode, + ListQueryBuilder, + RequestContext, + TransactionalConnection, + TranslatableSaver, + translateDeep, +} from '@vendure/core'; +import { In } from 'typeorm'; + +import { CampaignTranslation } from '../entities/campaign-translation.entity'; +import { Campaign } from '../entities/campaign.entity'; + +import { defaultCampaignData } from './default-campaigns/index'; + +@Injectable() +export class CampaignService { + constructor( + private readonly connection: TransactionalConnection, + private readonly listQueryBuilder: ListQueryBuilder, + private readonly collectionService: CollectionService, + private readonly translatableSaver: TranslatableSaver, + ) {} + + async initCampaigns() { + const item = await this.makeSureDefaultCampaigns(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await this.makeSureDefaultCollections(item!); + } + + findAll( + ctx: RequestContext, + options?: ListQueryOptions, + ): Promise>> { + return this.listQueryBuilder + .build(Campaign, options, { relations: ['promotion'], ctx }) + .getManyAndCount() + .then(([campaignItems, totalItems]) => { + const items = campaignItems.map(campaignItem => + translateDeep(campaignItem, ctx.languageCode, ['promotion']), + ); + return { + items, + totalItems, + }; + }); + } + + findOne(ctx: RequestContext, id: ID): Promise | undefined | null> { + return this.connection + .getRepository(ctx, Campaign) + .findOne({ where: { id }, relations: ['promotion'] }) + .then(campaignItem => { + return campaignItem && translateDeep(campaignItem, ctx.languageCode, ['promotion']); + }); + } + + findOneByCode(ctx: RequestContext, code: string): Promise | undefined | null> { + return this.connection + .getRepository(ctx, Campaign) + .findOne({ where: { code }, relations: ['promotion'] }) + .then(campaignItem => { + return campaignItem && translateDeep(campaignItem, ctx.languageCode, ['promotion']); + }); + } + + async create(ctx: RequestContext, input: any): Promise> { + const campaignItem = await this.translatableSaver.create({ + ctx, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input, + entityType: Campaign, + translationType: CampaignTranslation, + }); + return assertFound(this.findOne(ctx, campaignItem.id)); + } + + async update(ctx: RequestContext, input: any): Promise> { + const campaignItem = await this.translatableSaver.update({ + ctx, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input, + entityType: Campaign, + translationType: CampaignTranslation, + }); + return assertFound(this.findOne(ctx, campaignItem.id)); + } + + async delete(ctx: RequestContext, ids: ID[]): Promise { + const items = await this.connection.getRepository(ctx, Campaign).find({ + where: { + id: In(ids), + }, + }); + await this.connection.getRepository(ctx, Campaign).delete(items.map(s => String(s.id))); + + return { + result: DeletionResult.DELETED, + message: '', + }; + } + + private async makeSureDefaultCampaigns() { + const ctx = RequestContext.empty(); + const { items } = await this.findAll(ctx); + let item; + for (const campaignItem of defaultCampaignData()) { + const hasOne = items.find(s => s.code === campaignItem.code); + if (!hasOne) { + item = await this.create(ctx, campaignItem); + } else { + item = await this.update(ctx, { + ...campaignItem, + id: hasOne.id, + }); + } + } + return item; + } + + private async makeSureDefaultCollections(campaign: Translated) { + const ctx = RequestContext.empty(); + const { totalItems } = await this.collectionService.findAll(ctx); + if (totalItems > 0) { + return; + } + const parent = await this.collectionService.create(ctx, { + filters: [], + translations: [ + { + name: 'parent collection', + slug: 'parent-collection', + description: 'parent collection description', + languageCode: LanguageCode.en, + customFields: {}, + }, + ], + }); + await this.collectionService.create(ctx, { + filters: [], + parentId: parent?.id, + customFields: { + campaignId: campaign?.id, + }, + translations: [ + { + name: 'children collection', + slug: 'children-collection', + description: 'children collection description', + languageCode: LanguageCode.en, + customFields: {}, + }, + ], + }); + } +} diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/default-campaigns.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/default-campaigns.ts new file mode 100644 index 0000000000..c402876ba7 --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/default-campaigns.ts @@ -0,0 +1,23 @@ +import { LanguageCode } from '@vendure/common/lib/generated-types'; + +export const defaultCampaigns = () => + [ + { + code: 'discount', + campaignType: 'DirectDiscount', + needClaimCoupon: false, + enabled: true, + translations: [ + { + languageCode: LanguageCode.en, + name: 'Clearance Up to 70% Off frames', + shortDesc: 'Clearance Up to 70% Off frames', + }, + { + languageCode: LanguageCode.de, + name: 'Clearance Up to 70% Off frames of de', + shortDesc: 'Clearance Up to 70% Off frames of de', + }, + ], + }, + ] as any[]; diff --git a/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/index.ts b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/index.ts new file mode 100644 index 0000000000..31fab4a66d --- /dev/null +++ b/packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/index.ts @@ -0,0 +1,3 @@ +import { defaultCampaigns } from './default-campaigns'; + +export const defaultCampaignData = () => [...defaultCampaigns()]; From ec61b58f967eaac9f6001ad7a81e749e1ed1bd84 Mon Sep 17 00:00:00 2001 From: HoseinGhanbari <31586783+HoseinGhanbari@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:41:48 +0330 Subject: [PATCH 11/49] fix(admin-ui): Add missing RTL compatibility to some admin-ui components (#2451) --- .../app-shell/app-shell.component.ts | 23 +++++++---- .../notification/notification.component.html | 21 ++++------ .../notification/notification.component.ts | 20 +++++++++- .../theme-switcher.component.scss | 16 +++++++- .../localization/localization.service.spec.ts | 29 ++++++++++++++ .../localization/localization.service.ts | 34 ++++++++++++++++ .../dropdown/dropdown-menu.component.ts | 39 ++++++++++--------- .../modal-dialog/modal-dialog.component.html | 32 +++++++-------- .../modal-dialog/modal-dialog.component.ts | 30 ++++++++------ 9 files changed, 171 insertions(+), 73 deletions(-) create mode 100644 packages/admin-ui/src/lib/core/src/providers/localization/localization.service.spec.ts create mode 100644 packages/admin-ui/src/lib/core/src/providers/localization/localization.service.ts diff --git a/packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts b/packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts index 7fcf559b68..2a6d617200 100644 --- a/packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts +++ b/packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts @@ -13,6 +13,11 @@ import { I18nService } from '../../providers/i18n/i18n.service'; import { LocalStorageService } from '../../providers/local-storage/local-storage.service'; import { ModalService } from '../../providers/modal/modal.service'; import { UiLanguageSwitcherDialogComponent } from '../ui-language-switcher-dialog/ui-language-switcher-dialog.component'; +import { + LocalizationDirectionType, + LocalizationLanguageCodeType, + LocalizationService, +} from '../../providers/localization/localization.service'; @Component({ selector: 'vdr-app-shell', @@ -22,8 +27,8 @@ import { UiLanguageSwitcherDialogComponent } from '../ui-language-switcher-dialo export class AppShellComponent implements OnInit { version = ADMIN_UI_VERSION; userName$: Observable; - uiLanguageAndLocale$: Observable<[LanguageCode, string | undefined]>; - direction$: Observable<'ltr' | 'rtl'>; + uiLanguageAndLocale$: LocalizationLanguageCodeType; + direction$: LocalizationDirectionType; availableLanguages: LanguageCode[] = []; hideVendureBranding = getAppConfig().hideVendureBranding; pageTitle$: Observable; @@ -38,25 +43,27 @@ export class AppShellComponent implements OnInit { private modalService: ModalService, private localStorageService: LocalStorageService, private breadcrumbService: BreadcrumbService, + private localizationService: LocalizationService, ) {} ngOnInit() { + this.direction$ = this.localizationService.direction$; + + this.uiLanguageAndLocale$ = this.localizationService.uiLanguageAndLocale$; + this.userName$ = this.dataService.client .userStatus() .single$.pipe(map(data => data.userStatus.username)); - this.uiLanguageAndLocale$ = this.dataService.client - .uiState() - .stream$.pipe(map(({ uiState }) => [uiState.language, uiState.locale ?? undefined])); + this.availableLanguages = this.i18nService.availableLanguages; + this.pageTitle$ = this.breadcrumbService.breadcrumbs$.pipe( map(breadcrumbs => breadcrumbs[breadcrumbs.length - 1].label), ); + this.mainNavExpanded$ = this.dataService.client .uiState() .stream$.pipe(map(({ uiState }) => uiState.mainNavExpanded)); - this.direction$ = this.uiLanguageAndLocale$.pipe( - map(([languageCode]) => (this.i18nService.isRTL(languageCode) ? 'rtl' : 'ltr')), - ); } selectUiLanguage() { diff --git a/packages/admin-ui/src/lib/core/src/components/notification/notification.component.html b/packages/admin-ui/src/lib/core/src/components/notification/notification.component.html index d736ec5eb7..5f36f1b86d 100644 --- a/packages/admin-ui/src/lib/core/src/components/notification/notification.component.html +++ b/packages/admin-ui/src/lib/core/src/components/notification/notification.component.html @@ -1,15 +1,10 @@ -
+
{{ stringifyMessage(message) | translate: translationVars }} -
+
\ No newline at end of file diff --git a/packages/admin-ui/src/lib/core/src/components/notification/notification.component.ts b/packages/admin-ui/src/lib/core/src/components/notification/notification.component.ts index 145ddfb1f0..56bb80cf8f 100644 --- a/packages/admin-ui/src/lib/core/src/components/notification/notification.component.ts +++ b/packages/admin-ui/src/lib/core/src/components/notification/notification.component.ts @@ -1,13 +1,20 @@ -import { Component, ElementRef, HostListener, ViewChild } from '@angular/core'; +import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'; import { NotificationType } from '../../providers/notification/notification.service'; +import { + LocalizationDirectionType, + LocalizationService, +} from '../../providers/localization/localization.service'; + @Component({ selector: 'vdr-notification', templateUrl: './notification.component.html', styleUrls: ['./notification.component.scss'], }) -export class NotificationComponent { +export class NotificationComponent implements OnInit { + direction$: LocalizationDirectionType; + @ViewChild('wrapper', { static: true }) wrapper: ElementRef; offsetTop = 0; message = ''; @@ -18,6 +25,15 @@ export class NotificationComponent { /* */ }; + /** + * + */ + constructor(private localizationService: LocalizationService) {} + + ngOnInit(): void { + this.direction$ = this.localizationService.direction$; + } + registerOnClickFn(fn: () => void): void { this.onClickFn = fn; } diff --git a/packages/admin-ui/src/lib/core/src/components/theme-switcher/theme-switcher.component.scss b/packages/admin-ui/src/lib/core/src/components/theme-switcher/theme-switcher.component.scss index 7cea448e4b..f76dba5bfd 100644 --- a/packages/admin-ui/src/lib/core/src/components/theme-switcher/theme-switcher.component.scss +++ b/packages/admin-ui/src/lib/core/src/components/theme-switcher/theme-switcher.component.scss @@ -1,6 +1,6 @@ :host { display: flex; - justify-content: start; + justify-content: flex-start; align-items: center; } @@ -27,10 +27,24 @@ button.theme-toggle { left: 0px; opacity: 1; } + &.default.active { color: #d6ae3f; } + &.dark.active { color: #ffdf3a; } } + +:host-context([dir='rtl']) { + .theme-icon { + left: auto; + right: 6px; + + &.active { + left: auto; + right: 0px; + } + } +} \ No newline at end of file diff --git a/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.spec.ts b/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.spec.ts new file mode 100644 index 0000000000..11b0e789b4 --- /dev/null +++ b/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing'; + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { TestingCommonModule } from '../../../../../testing/testing-common.module'; +import { MockI18nService } from '../i18n/i18n.service.mock'; +import { DataService } from '../../data/providers/data.service'; +import { I18nService } from '../../providers/i18n/i18n.service'; +import { LocalizationService } from './localization.service'; + +describe('LocalizationService', () => { + let service: LocalizationService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestingCommonModule], + providers: [ + LocalizationService, + { provide: I18nService, useClass: MockI18nService }, + { provide: DataService, useClass: class {} }, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }); + service = TestBed.inject(LocalizationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.ts b/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.ts new file mode 100644 index 0000000000..9b40ffc26f --- /dev/null +++ b/packages/admin-ui/src/lib/core/src/providers/localization/localization.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { Observable, map } from 'rxjs'; + +import { DataService } from '../../data/providers/data.service'; +import { I18nService } from '../../providers/i18n/i18n.service'; +import { LanguageCode } from '../../common/generated-types'; + +export type LocalizationDirectionType = Observable<'ltr' | 'rtl'>; +export type LocalizationLanguageCodeType = Observable<[LanguageCode, string | undefined]>; + +/** + * @description + * Provides localization helper functionality. + * + */ +@Injectable({ + providedIn: 'root', +}) +export class LocalizationService { + uiLanguageAndLocale$: LocalizationLanguageCodeType; + direction$: LocalizationDirectionType; + + constructor(private i18nService: I18nService, private dataService: DataService) { + this.uiLanguageAndLocale$ = this.dataService.client + ?.uiState() + ?.stream$?.pipe(map(({ uiState }) => [uiState.language, uiState.locale ?? undefined])); + + this.direction$ = this.uiLanguageAndLocale$?.pipe( + map(([languageCode]) => { + return this.i18nService.isRTL(languageCode) ? 'rtl' : 'ltr'; + }), + ); + } +} diff --git a/packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.ts b/packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.ts index 25b37db1f5..80f190f0be 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.ts @@ -1,18 +1,9 @@ -import { - ConnectedPosition, - HorizontalConnectionPos, - Overlay, - OverlayRef, - PositionStrategy, - VerticalConnectionPos, -} from '@angular/cdk/overlay'; +import { ConnectedPosition, Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; import { AfterViewInit, ChangeDetectionStrategy, Component, - ContentChild, - ElementRef, HostListener, Input, OnDestroy, @@ -23,7 +14,10 @@ import { } from '@angular/core'; import { Subscription } from 'rxjs'; -import { DropdownTriggerDirective } from './dropdown-trigger.directive'; +import { + LocalizationDirectionType, + LocalizationService, +} from '../../../providers/localization/localization.service'; import { DropdownComponent } from './dropdown.component'; export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; @@ -41,14 +35,16 @@ export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'botto selector: 'vdr-dropdown-menu', template: ` -