@akinon/pz-theme 2.0.125-rc.1 → 2.0.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,76 +1,12 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.125-rc.1
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [86187b1c2]
8
- - Updated dependencies [0cf9ea239]
9
- - Updated dependencies [324f97d55]
10
- - Updated dependencies [51ea06888]
11
- - Updated dependencies
12
- - Updated dependencies [2fea41430]
13
- - Updated dependencies [b55acb768]
14
- - Updated dependencies [760258c1c]
15
- - Updated dependencies [143be2b9d]
16
- - Updated dependencies [7889b08fe]
17
- - Updated dependencies [9f8cd3bc5]
18
- - Updated dependencies [bfafa3f49]
19
- - Updated dependencies [57d7eb305]
20
- - Updated dependencies [d99a6a7d5]
21
- - Updated dependencies [9db81a714]
22
- - Updated dependencies [591e345e1]
23
- - Updated dependencies [4de5303c5]
24
- - Updated dependencies [95b139dc1]
25
- - Updated dependencies [1d00f2d06]
26
- - Updated dependencies [4ac7b2a1e]
27
- - Updated dependencies [e9598c71d]
28
- - Updated dependencies [4998a9631]
29
- - Updated dependencies [804d2bd6c]
30
- - Updated dependencies [3909d3224]
31
- - Updated dependencies [6a3d8a631]
32
- - Updated dependencies [e18836b20]
33
- - Updated dependencies [b1111d7b1]
34
- - Updated dependencies [1595d458]
35
- - Updated dependencies [dca12811]
36
- - @akinon/next@2.0.125-rc.1
37
- - @akinon/theme-contract@2.0.125-rc.1
38
-
39
- ## 2.0.125-rc.0
40
-
41
- ### Patch Changes
42
-
43
- - Updated dependencies [86187b1c2]
44
- - Updated dependencies [0cf9ea239]
45
- - Updated dependencies [324f97d55]
46
- - Updated dependencies [51ea06888]
47
- - Updated dependencies
48
- - Updated dependencies [2fea41430]
49
- - Updated dependencies [b55acb768]
50
- - Updated dependencies [760258c1c]
51
- - Updated dependencies [143be2b9d]
52
- - Updated dependencies [7889b08fe]
53
- - Updated dependencies [9f8cd3bc5]
54
- - Updated dependencies [bfafa3f49]
55
- - Updated dependencies [57d7eb305]
56
- - Updated dependencies [d99a6a7d5]
57
- - Updated dependencies [9db81a714]
58
- - Updated dependencies [591e345e1]
59
- - Updated dependencies [4de5303c5]
60
- - Updated dependencies [95b139dc1]
61
- - Updated dependencies [1d00f2d06]
62
- - Updated dependencies [4ac7b2a1e]
63
- - Updated dependencies [e9598c71d]
64
- - Updated dependencies [4998a9631]
65
- - Updated dependencies [804d2bd6c]
66
- - Updated dependencies [3909d3224]
67
- - Updated dependencies [6a3d8a631]
68
- - Updated dependencies [e18836b20]
69
- - Updated dependencies [b1111d7b1]
70
- - Updated dependencies [1595d458]
71
- - Updated dependencies [dca12811]
72
- - @akinon/next@2.0.125-rc.0
73
- - @akinon/theme-contract@2.0.125-rc.0
3
+ ## 2.0.125
4
+
5
+ ### Patch Changes
6
+
7
+ - aaa4433: ZERO-5122: resolve search box texts per storefront language
8
+ - @akinon/next@2.0.125
9
+ - @akinon/theme-contract@2.0.125
74
10
 
75
11
  ## 2.0.124
76
12
 
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@akinon/pz-theme",
3
3
  "description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
4
- "version": "2.0.125-rc.1",
4
+ "version": "2.0.125",
5
5
  "license": "MIT",
6
6
  "main": "src/index.ts",
7
7
  "scripts": {
8
8
  "test": "jest"
9
9
  },
10
10
  "peerDependencies": {
11
- "@akinon/next": "2.0.125-rc.1",
11
+ "@akinon/next": "2.0.125",
12
12
  "react": "^18.0.0 || ^19.0.0",
13
13
  "react-dom": "^18.0.0 || ^19.0.0"
14
14
  },
15
15
  "dependencies": {
16
- "@akinon/theme-contract": "2.0.125-rc.1",
16
+ "@akinon/theme-contract": "2.0.125",
17
17
  "clsx": "2.1.1",
18
18
  "lottie-web": "^5.13.0",
19
19
  "react-multi-carousel": "2.8.4",
@@ -21,7 +21,7 @@
21
21
  "zod": "4.4.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@akinon/next": "2.0.125-rc.1",
24
+ "@akinon/next": "2.0.125",
25
25
  "@testing-library/jest-dom": "5.16.4",
26
26
  "@testing-library/react": "16.3.2",
27
27
  "@testing-library/user-event": "14.1.1",
@@ -0,0 +1,164 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+
4
+ let localizationValue: Record<string, unknown> = {};
5
+
6
+ jest.mock('@akinon/next/hooks', () => ({
7
+ __esModule: true,
8
+ useLocalization: () => localizationValue
9
+ }));
10
+
11
+ jest.mock('next/navigation', () => ({
12
+ useRouter: () => ({ push: jest.fn() }),
13
+ usePathname: () => '/'
14
+ }));
15
+
16
+ import SearchBoxBlock from '../search-box-block';
17
+ import type { BlockRendererProps } from '../block-renderer-registry';
18
+
19
+ const setLocalization = (
20
+ locale: string,
21
+ locales: string[] = ['tr', 'en'],
22
+ defaultLocaleValue = 'tr'
23
+ ) => {
24
+ localizationValue = {
25
+ locale,
26
+ locales: locales.map((value) => ({ value, label: value.toUpperCase() })),
27
+ defaultLocaleValue,
28
+ localeUrlStrategy: 'hide_default_locale'
29
+ };
30
+ };
31
+
32
+ const TR = 'Ürün, kategori, renk arayabilirsiniz';
33
+ const EN = 'You can search for products, categories, or colors.';
34
+
35
+ const mount = (
36
+ properties: Record<string, unknown>,
37
+ extra: Partial<BlockRendererProps> = {}
38
+ ) =>
39
+ render(
40
+ <SearchBoxBlock
41
+ {...({
42
+ block: {
43
+ id: 'search',
44
+ type: 'search-box',
45
+ properties: { mode: 'icon', ...properties }
46
+ },
47
+ placeholderId: 'p',
48
+ sectionId: 's'
49
+ } as unknown as BlockRendererProps)}
50
+ {...extra}
51
+ />
52
+ );
53
+
54
+ const open = (container: HTMLElement) => {
55
+ fireEvent.click(container.querySelector('[data-search-box]') as HTMLElement);
56
+ return container;
57
+ };
58
+
59
+ const placeholderOf = (properties: Record<string, unknown>) =>
60
+ open(mount(properties).container)
61
+ .querySelector('[data-search-input]')
62
+ ?.getAttribute('placeholder');
63
+
64
+ beforeEach(() => setLocalization('tr'));
65
+
66
+ describe('placeholder', () => {
67
+ it('keeps reading a plain string as it always has', () => {
68
+ expect(placeholderOf({ placeholder: TR })).toBe(TR);
69
+ });
70
+
71
+ it('picks the entry for the page’s language from a per-language map', () => {
72
+ expect(placeholderOf({ placeholder: { tr: TR, en: EN } })).toBe(TR);
73
+
74
+ setLocalization('en');
75
+ expect(placeholderOf({ placeholder: { tr: TR, en: EN } })).toBe(EN);
76
+ });
77
+
78
+ it('reads a per-language map typed into the field as JSON', () => {
79
+ setLocalization('en');
80
+ expect(
81
+ placeholderOf({ placeholder: JSON.stringify({ tr: TR, en: EN }) })
82
+ ).toBe(EN);
83
+ });
84
+
85
+ it('reads the JSON map the editor stores under a breakpoint', () => {
86
+ setLocalization('en');
87
+ expect(
88
+ placeholderOf({
89
+ placeholder: { desktop: JSON.stringify({ tr: TR, en: EN }) }
90
+ })
91
+ ).toBe(EN);
92
+
93
+ setLocalization('tr');
94
+ expect(
95
+ placeholderOf({
96
+ placeholder: { desktop: JSON.stringify({ tr: TR, en: EN }) }
97
+ })
98
+ ).toBe(TR);
99
+ });
100
+
101
+ it('still honours a per-breakpoint value, with or without languages inside', () => {
102
+ expect(placeholderOf({ placeholder: { desktop: EN } })).toBe(EN);
103
+ expect(
104
+ placeholderOf({ placeholder: { desktop: { tr: TR, en: EN } } })
105
+ ).toBe(TR);
106
+ expect(
107
+ placeholderOf({
108
+ placeholder: { tr: { desktop: TR }, en: { desktop: EN } }
109
+ })
110
+ ).toBe(TR);
111
+ });
112
+
113
+ it('falls back to the default language, then to the block’s own wording', () => {
114
+ setLocalization('en');
115
+ expect(placeholderOf({ placeholder: { tr: TR, en: '' } })).toBe(TR);
116
+ expect(placeholderOf({ placeholder: { tr: '', en: '' } })).toBe('Search');
117
+ expect(placeholderOf({})).toBe('Search');
118
+ });
119
+
120
+ it('does not treat a locale-looking map as languages the store does not have', () => {
121
+ setLocalization('de', ['de']);
122
+ expect(placeholderOf({ placeholder: { tr: TR, en: EN } })).toBe('Search');
123
+ });
124
+ });
125
+
126
+ describe('the other shopper-facing texts', () => {
127
+ it('resolve the trigger, close and submit labels per language', () => {
128
+ setLocalization('en');
129
+ const { container } = mount({
130
+ triggerLabel: { tr: 'Ara', en: 'Search' },
131
+ closeLabel: { tr: 'Aramayı kapat', en: 'Close search' },
132
+ submitLabel: { tr: 'Ara', en: 'Go' },
133
+ showSubmit: true,
134
+ showClose: true
135
+ });
136
+
137
+ expect(
138
+ container.querySelector('[data-search-box]')?.getAttribute('aria-label')
139
+ ).toBe('Search');
140
+
141
+ open(container);
142
+
143
+ expect(
144
+ container.querySelector('[data-search-close]')?.getAttribute('aria-label')
145
+ ).toBe('Close search');
146
+ expect(container.querySelector('button[type="submit"]')?.textContent).toBe(
147
+ 'Go'
148
+ );
149
+ });
150
+
151
+ it('lets the trigger keep announcing the placeholder when no label is set', () => {
152
+ const { container } = mount({ placeholder: { tr: TR, en: EN } });
153
+
154
+ expect(
155
+ container.querySelector('[data-search-box]')?.getAttribute('aria-label')
156
+ ).toBe(TR);
157
+ });
158
+
159
+ it('leaves the non-text settings alone', () => {
160
+ const { container } = mount({ mode: { tr: 'inline', en: 'icon' } });
161
+
162
+ expect(container.querySelector('[data-search-box]')).not.toBeNull();
163
+ });
164
+ });
@@ -7,6 +7,10 @@ import { emitCommerceEvent } from '../utils/analytics';
7
7
  import { useLocalizeHref } from '../hooks/use-localize-href';
8
8
  import { applySearchTermTransform } from '../utils/search-transform';
9
9
  import { useOverlay } from '../hooks/use-overlay';
10
+ import {
11
+ resolveLocalizedValue,
12
+ useValueLocale
13
+ } from '../utils/localized-value';
10
14
  import ThemeBlock from '../theme-block';
11
15
  import { BlockRendererProps } from './block-renderer-registry';
12
16
  import { DesignerPlaceholder, readToggle } from './product-block-utils';
@@ -61,21 +65,35 @@ const SearchBoxBlock = ({
61
65
  isDesigner && resolveForceOpen(block.styles, currentBreakpoint);
62
66
  const panelOpen = open || forcedOpen;
63
67
 
68
+ const valueLocale = useValueLocale();
64
69
  const prop = (key: string, fallback: string) =>
65
70
  String(
66
71
  getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
67
72
  );
73
+ const text = (key: string, fallback: string) => {
74
+ const localized = resolveLocalizedValue(
75
+ block.properties?.[key],
76
+ valueLocale
77
+ );
78
+ const value = resolveLocalizedValue(
79
+ getResponsiveValue(localized, currentBreakpoint, ''),
80
+ valueLocale
81
+ );
82
+ return value === undefined || value === null || value === ''
83
+ ? fallback
84
+ : String(value);
85
+ };
68
86
 
69
87
  const mode = prop('mode', 'icon');
70
- const placeholder = prop('placeholder', 'Search');
88
+ const placeholder = text('placeholder', 'Search');
71
89
  const searchUrl = prop('searchUrl', '/list');
72
90
  const queryParam = prop('queryParam', 'search_text');
73
- const closeLabel = prop('closeLabel', 'Close search');
74
- const submitLabel = prop('submitLabel', 'Search');
91
+ const closeLabel = text('closeLabel', 'Close search');
92
+ const submitLabel = text('submitLabel', 'Search');
75
93
  const submitStyle = prop('submitStyle', 'label');
76
94
  // Empty on purpose: the trigger has always announced itself with the
77
95
  // placeholder, so an unset label must keep doing exactly that.
78
- const triggerLabel = prop('triggerLabel', '');
96
+ const triggerLabel = text('triggerLabel', '');
79
97
  const showIcon = readToggle(
80
98
  block.properties?.showIcon,
81
99
  currentBreakpoint,