@scrabble-solver/scrabble-solver 2.9.3 → 2.10.0

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.
Files changed (118) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +11 -11
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/{429.js → 131.js} +2 -3652
  16. package/.next/server/chunks/413.js +367 -185
  17. package/.next/server/chunks/44.js +71 -45
  18. package/.next/server/chunks/515.js +1026 -480
  19. package/.next/server/chunks/911.js +96 -2
  20. package/.next/server/middleware-build-manifest.js +1 -1
  21. package/.next/server/pages/404.html +2 -2
  22. package/.next/server/pages/404.js.nft.json +1 -1
  23. package/.next/server/pages/500.html +2 -2
  24. package/.next/server/pages/_app.js.nft.json +1 -1
  25. package/.next/server/pages/_error.js.nft.json +1 -1
  26. package/.next/server/pages/api/dictionary/[locale]/[word].js +48 -10
  27. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  28. package/.next/server/pages/api/dictionary/[locale].js +1 -8
  29. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  30. package/.next/server/pages/api/solve.js +62 -49
  31. package/.next/server/pages/api/solve.js.nft.json +1 -1
  32. package/.next/server/pages/api/verify.js +2 -9
  33. package/.next/server/pages/api/verify.js.nft.json +1 -1
  34. package/.next/server/pages/index.html +9 -1
  35. package/.next/server/pages/index.js +43 -36
  36. package/.next/server/pages/index.js.nft.json +1 -1
  37. package/.next/server/pages/index.json +1 -1
  38. package/.next/server/pages-manifest.json +2 -2
  39. package/.next/static/chunks/368-d423e70be6c0c473.js +1 -0
  40. package/.next/static/chunks/pages/{404-8650986dc56dab75.js → 404-932294135c3206dd.js} +1 -1
  41. package/.next/static/chunks/pages/_app-3f5508a5f544d9eb.js +1 -0
  42. package/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +1 -0
  43. package/.next/static/css/6b1833fd19d3a74a.css +1 -0
  44. package/.next/static/css/a6154e4ca046ca13.css +1 -0
  45. package/.next/static/css/bad53af6f8616677.css +1 -0
  46. package/.next/static/vscqn7BEtAxJteWSwNnas/_buildManifest.js +1 -0
  47. package/.next/static/{s5pE_3C3ebZuSfaqE7xat → vscqn7BEtAxJteWSwNnas}/_ssgManifest.js +0 -0
  48. package/.next/trace +52 -52
  49. package/package.json +9 -9
  50. package/src/components/Board/components/Cell/Cell.module.scss +45 -9
  51. package/src/components/Board/hooks/useGrid.ts +4 -2
  52. package/src/components/Dictionary/Dictionary.module.scss +8 -1
  53. package/src/components/EmptyState/EmptyState.tsx +6 -2
  54. package/src/components/Loading/Loading.tsx +13 -2
  55. package/src/components/NavButtons/NavButtons.module.scss +8 -7
  56. package/src/components/NavButtons/NavButtons.tsx +35 -28
  57. package/src/components/PlainTiles/PlainTiles.tsx +7 -1
  58. package/src/components/PlainTiles/Tile.tsx +4 -3
  59. package/src/components/Rack/Rack.tsx +4 -2
  60. package/src/components/Radio/Radio.module.scss +1 -1
  61. package/src/components/RemainingTiles/Character.tsx +7 -2
  62. package/src/components/RemainingTiles/RemainingTiles.tsx +28 -20
  63. package/src/components/Results/Cell.tsx +5 -3
  64. package/src/components/Results/Result.tsx +8 -3
  65. package/src/components/Results/Results.module.scss +31 -9
  66. package/src/components/Results/Results.tsx +6 -2
  67. package/src/components/Results/getColumns.ts +58 -0
  68. package/src/components/Settings/components/ConfigSetting/ConfigSetting.module.scss +1 -0
  69. package/src/components/Settings/components/LocaleSetting/LocaleSetting.module.scss +12 -1
  70. package/src/components/Settings/components/LocaleSetting/LocaleSetting.tsx +1 -1
  71. package/src/components/Settings/components/LocaleSetting/options.ts +7 -1
  72. package/src/components/Sidebar/Sidebar.module.scss +42 -8
  73. package/src/components/SvgFontCss/SvgFontCss.tsx +8 -7
  74. package/src/components/SvgFontCss/createCss.ts +11 -0
  75. package/src/components/SvgFontCss/createStyle.ts +9 -0
  76. package/src/components/SvgFontCss/createSvg.ts +10 -0
  77. package/src/components/SvgFontFix/SvgFontFix.module.scss +5 -0
  78. package/src/components/SvgFontFix/SvgFontFix.tsx +21 -0
  79. package/src/components/SvgFontFix/index.ts +1 -0
  80. package/src/components/Tile/Tile.module.scss +10 -2
  81. package/src/components/Tile/Tile.tsx +4 -0
  82. package/src/components/Tile/TilePure.tsx +3 -1
  83. package/src/components/Words/Words.tsx +4 -3
  84. package/src/components/index.ts +1 -0
  85. package/src/hooks/index.ts +2 -0
  86. package/src/hooks/useDirection.ts +22 -0
  87. package/src/hooks/useLanguage.ts +22 -0
  88. package/src/i18n/constants.ts +53 -0
  89. package/src/i18n/fa.json +56 -0
  90. package/src/i18n/i18n.ts +22 -0
  91. package/src/i18n/index.ts +2 -20
  92. package/src/icons/FlagFa.svg +95 -0
  93. package/src/icons/index.ts +1 -0
  94. package/src/lib/createComparator.ts +22 -0
  95. package/src/lib/createKeyComparator.ts +4 -2
  96. package/src/lib/createStringComparator.ts +5 -0
  97. package/src/lib/detectLocale.ts +4 -0
  98. package/src/lib/getRemainingTiles.ts +4 -2
  99. package/src/lib/getRemainingTilesGroups.ts +48 -23
  100. package/src/lib/index.ts +2 -2
  101. package/src/lib/sortResults.ts +11 -9
  102. package/src/lib/unorderedArraysEqual.ts +3 -2
  103. package/src/pages/api/verify.ts +1 -1
  104. package/src/pages/index.module.scss +7 -18
  105. package/src/pages/index.tsx +10 -2
  106. package/src/service-worker/routeVerifyRequests.ts +1 -1
  107. package/src/state/selectors.ts +5 -5
  108. package/src/styles/global.scss +6 -1
  109. package/.next/static/chunks/880-18e3aea6915aebe7.js +0 -1
  110. package/.next/static/chunks/pages/_app-183f598b1d4d480b.js +0 -1
  111. package/.next/static/chunks/pages/index-f1cef1e67e14d022.js +0 -1
  112. package/.next/static/css/751e8a14776d05d8.css +0 -1
  113. package/.next/static/css/ad2a08918868cad8.css +0 -1
  114. package/.next/static/css/e8de67ad5ea35427.css +0 -1
  115. package/.next/static/s5pE_3C3ebZuSfaqE7xat/_buildManifest.js +0 -1
  116. package/src/components/Results/constants.ts +0 -42
  117. package/src/lib/comparator.ts +0 -16
  118. package/src/lib/stringComparator.ts +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.9.3",
3
+ "version": "2.10.0",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,13 +31,13 @@
31
31
  "@kamilmielnik/trie": "^2.0.1",
32
32
  "@popperjs/core": "^2.11.6",
33
33
  "@reduxjs/toolkit": "^1.8.6",
34
- "@scrabble-solver/configs": "^2.9.3",
35
- "@scrabble-solver/constants": "^2.9.3",
36
- "@scrabble-solver/dictionaries": "^2.9.3",
37
- "@scrabble-solver/logger": "^2.9.3",
38
- "@scrabble-solver/solver": "^2.9.3",
39
- "@scrabble-solver/types": "^2.9.3",
40
- "@scrabble-solver/word-definitions": "^2.9.3",
34
+ "@scrabble-solver/configs": "^2.10.0",
35
+ "@scrabble-solver/constants": "^2.10.0",
36
+ "@scrabble-solver/dictionaries": "^2.10.0",
37
+ "@scrabble-solver/logger": "^2.10.0",
38
+ "@scrabble-solver/solver": "^2.10.0",
39
+ "@scrabble-solver/types": "^2.10.0",
40
+ "@scrabble-solver/word-definitions": "^2.10.0",
41
41
  "classnames": "^2.3.2",
42
42
  "next": "^12.3.1",
43
43
  "normalize.css": "^8.0.1",
@@ -74,5 +74,5 @@
74
74
  "sass": "^1.55.0",
75
75
  "workbox-webpack-plugin": "^6.5.4"
76
76
  },
77
- "gitHead": "ef8d030cbb76256a377f747f7ebe4b85b7eba61e"
77
+ "gitHead": "7fd0f6bf536fca88c18ce02e320da71b0c6d6f61"
78
78
  }
@@ -66,6 +66,10 @@ $icon-size: 16px;
66
66
  &:before {
67
67
  font-size: 75%;
68
68
  color: white;
69
+
70
+ [lang='fa-IR'] & {
71
+ font-family: 'Open Sans', sans-serif;
72
+ }
69
73
  }
70
74
  }
71
75
 
@@ -122,13 +126,20 @@ $icon-size: 16px;
122
126
  .actions {
123
127
  display: none;
124
128
  position: absolute;
125
- left: calc(100% + var(--spacing--s) - #{$icon-size});
126
129
  top: -$icon-size;
127
130
  z-index: 2;
128
131
  transition: var(--transition);
129
132
  pointer-events: none;
130
133
  box-shadow: var(--box-shadow);
131
134
  border-radius: var(--border--radius);
135
+
136
+ [dir='ltr'] & {
137
+ left: calc(100% + var(--spacing--s) - #{$icon-size});
138
+ }
139
+
140
+ [dir='rtl'] & {
141
+ right: calc(100% + var(--spacing--s) - #{$icon-size});
142
+ }
132
143
  }
133
144
 
134
145
  .action {
@@ -142,8 +153,6 @@ $icon-size: 16px;
142
153
  box-sizing: content-box;
143
154
  background-color: white;
144
155
  border: var(--border);
145
- border-top-left-radius: var(--border--radius);
146
- border-bottom-left-radius: var(--border--radius);
147
156
  font-size: var(--font--size--m);
148
157
  line-height: $icon-size;
149
158
  color: var(--color--foreground--secondary);
@@ -151,14 +160,37 @@ $icon-size: 16px;
151
160
  cursor: pointer;
152
161
 
153
162
  & + & {
154
- border-left: none;
155
- border-top-left-radius: 0;
156
- border-bottom-left-radius: 0;
163
+ [dir='ltr'] & {
164
+ border-left: none;
165
+ }
166
+
167
+ [dir='rtl'] & {
168
+ border-right: none;
169
+ }
170
+ }
171
+
172
+ [dir='ltr'] & {
173
+ &:first-child {
174
+ border-top-left-radius: var(--border--radius);
175
+ border-bottom-left-radius: var(--border--radius);
176
+ }
177
+
178
+ &:last-child {
179
+ border-top-right-radius: var(--border--radius);
180
+ border-bottom-right-radius: var(--border--radius);
181
+ }
157
182
  }
158
183
 
159
- &:last-child {
160
- border-top-right-radius: var(--border--radius);
161
- border-bottom-right-radius: var(--border--radius);
184
+ [dir='rtl'] & {
185
+ &:first-child {
186
+ border-top-right-radius: var(--border--radius);
187
+ border-bottom-right-radius: var(--border--radius);
188
+ }
189
+
190
+ &:last-child {
191
+ border-top-left-radius: var(--border--radius);
192
+ border-bottom-left-radius: var(--border--radius);
193
+ }
162
194
  }
163
195
 
164
196
  &,
@@ -177,6 +209,10 @@ $icon-size: 16px;
177
209
  .toggleDirection {
178
210
  &.right {
179
211
  transform: rotate(-90deg);
212
+
213
+ [dir='rtl'] & {
214
+ transform: rotate(90deg);
215
+ }
180
216
  }
181
217
  }
182
218
 
@@ -235,7 +235,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
235
235
  if (isCtrl(event)) {
236
236
  onDirectionToggle();
237
237
  } else {
238
- changeActiveIndex(-1, 0);
238
+ const isRtl = document.body.parentElement?.dir === 'rtl';
239
+ changeActiveIndex(isRtl ? 1 : -1, 0);
239
240
  }
240
241
  },
241
242
  onArrowRight: (event) => {
@@ -244,7 +245,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
244
245
  if (isCtrl(event)) {
245
246
  onDirectionToggle();
246
247
  } else {
247
- changeActiveIndex(1, 0);
248
+ const isRtl = document.body.parentElement?.dir === 'rtl';
249
+ changeActiveIndex(isRtl ? -1 : 1, 0);
248
250
  }
249
251
  },
250
252
  onArrowUp: (event) => {
@@ -48,7 +48,14 @@
48
48
 
49
49
  .definitions {
50
50
  margin: 0;
51
- padding-left: var(--spacing--l);
51
+
52
+ [dir='ltr'] & {
53
+ padding-left: var(--spacing--l);
54
+ }
55
+
56
+ [dir='rtl'] & {
57
+ padding-right: var(--spacing--l);
58
+ }
52
59
  }
53
60
 
54
61
  .definition {
@@ -1,8 +1,9 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent, ReactNode, useMemo } from 'react';
3
3
 
4
+ import { LOCALE_FEATURES } from 'i18n';
4
5
  import { COLOR_BLUE, COLOR_GREEN, COLOR_RED, COLOR_YELLOW } from 'parameters';
5
- import { useTranslate } from 'state';
6
+ import { selectLocale, useTranslate, useTypedSelector } from 'state';
6
7
  import { Translations } from 'types';
7
8
 
8
9
  import PlainTiles from '../PlainTiles';
@@ -31,8 +32,11 @@ const COLORS_PER_TYPE: Record<Props['type'], string> = {
31
32
 
32
33
  const EmptyState: FunctionComponent<Props> = ({ className, children, type }) => {
33
34
  const translate = useTranslate();
35
+ const locale = useTypedSelector(selectLocale);
36
+ const { direction } = LOCALE_FEATURES[locale];
34
37
  const title = useMemo(() => translate(TITLE_KEY_PER_TYPE[type]), [translate]);
35
- const content = useMemo(() => [[title.toUpperCase()]], [title]);
38
+ const message = direction === 'ltr' ? title : title.split('').reverse().join('');
39
+ const content = useMemo(() => [[message.toUpperCase()]], [title]);
36
40
 
37
41
  return (
38
42
  <div className={classNames(styles.emptyState, className)}>
@@ -1,7 +1,8 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent, useMemo } from 'react';
3
3
 
4
- import { useTranslate } from 'state';
4
+ import { LOCALE_FEATURES } from 'i18n';
5
+ import { selectLocale, useTranslate, useTypedSelector } from 'state';
5
6
 
6
7
  import PlainTiles from '../PlainTiles';
7
8
 
@@ -13,9 +14,19 @@ interface Props {
13
14
  wave?: boolean;
14
15
  }
15
16
 
17
+ const prepareContent = (message: string): string[][] => {
18
+ const uppercased = message.toLocaleUpperCase();
19
+ const parts = uppercased.split(' ');
20
+ return [parts];
21
+ };
22
+
16
23
  const Loading: FunctionComponent<Props> = ({ className, wave = true }) => {
17
24
  const translate = useTranslate();
18
- const content = useMemo<string[][]>(() => [[translate('common.loading').toUpperCase()]], [translate]);
25
+ const locale = useTypedSelector(selectLocale);
26
+ const { direction } = LOCALE_FEATURES[locale];
27
+ const translation = translate('common.loading');
28
+ const message = direction === 'ltr' ? translation : translation.split('').reverse().join('');
29
+ const content = useMemo(() => prepareContent(message), [message]);
19
30
 
20
31
  return (
21
32
  <div className={classNames(styles.loading, className)}>
@@ -1,25 +1,26 @@
1
- $size: 32px;
2
-
3
1
  .navButtons {
4
2
  display: flex;
5
3
  align-items: center;
4
+ gap: var(--spacing--l);
5
+ gap: calc(var(--spacing--l) - var(--spacing--xs));
6
6
  }
7
7
 
8
8
  .button,
9
9
  .separator {
10
- margin-right: calc(var(--spacing--l) - 2 * var(--spacing--xs));
11
-
12
10
  &:last-child {
13
11
  margin-right: 0;
14
12
  }
15
13
  }
16
14
 
15
+ .group {
16
+ display: flex;
17
+ gap: var(--spacing--l);
18
+ }
19
+
17
20
  .separator {
18
- margin-right: calc(var(--spacing--l) - var(--spacing--xs));
19
- margin-left: var(--spacing--xs);
20
21
  border-right: var(--border);
21
22
  border-color: var(--color--inactive);
22
- height: calc(#{$size} - 2 * var(--spacing--xs));
23
+ height: 32px;
23
24
  }
24
25
 
25
26
  .error {
@@ -30,44 +30,51 @@ const NavButtons: FunctionComponent<Props> = ({
30
30
 
31
31
  return (
32
32
  <div className={styles.navButtons}>
33
- <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
33
+ <div className={styles.group}>
34
+ <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
35
+ </div>
34
36
 
35
37
  <div className={styles.separator} />
36
38
 
37
- <SquareButton
38
- className={classNames(styles.button, {
39
- [styles.error]: hasOverusedTiles,
40
- })}
41
- Icon={Sack}
42
- tooltip={translate('remaining-tiles')}
43
- onClick={onShowRemainingTiles}
44
- />
39
+ <div className={styles.group}>
40
+ <SquareButton
41
+ className={classNames(styles.button, {
42
+ [styles.error]: hasOverusedTiles,
43
+ })}
44
+ Icon={Sack}
45
+ tooltip={translate('remaining-tiles')}
46
+ onClick={onShowRemainingTiles}
47
+ />
45
48
 
46
- <SquareButton
47
- className={classNames(styles.button, {
48
- [styles.error]: hasInvalidWords,
49
- })}
50
- Icon={BookHalf}
51
- tooltip={translate('words')}
52
- onClick={onShowWords}
53
- />
49
+ <SquareButton
50
+ className={classNames(styles.button, {
51
+ [styles.error]: hasInvalidWords,
52
+ })}
53
+ Icon={BookHalf}
54
+ tooltip={translate('words')}
55
+ onClick={onShowWords}
56
+ />
57
+ </div>
54
58
 
55
59
  <div className={styles.separator} />
56
60
 
57
- <SquareButton.Link
58
- className={styles.button}
59
- href={GITHUB_PROJECT_URL}
60
- Icon={Github}
61
- rel="noopener noreferrer"
62
- target="_blank"
63
- tooltip={translate('github')}
64
- />
61
+ <div className={styles.group}>
62
+ <SquareButton.Link
63
+ className={styles.button}
64
+ href={GITHUB_PROJECT_URL}
65
+ Icon={Github}
66
+ rel="noopener noreferrer"
67
+ target="_blank"
68
+ tooltip={translate('github')}
69
+ />
70
+ </div>
65
71
 
66
72
  <div className={styles.separator} />
67
73
 
68
- <SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
69
-
70
- <SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
74
+ <div className={styles.group}>
75
+ <SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
76
+ <SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
77
+ </div>
71
78
  </div>
72
79
  );
73
80
  };
@@ -1,6 +1,9 @@
1
1
  import classNames from 'classnames';
2
2
  import { CSSProperties, FunctionComponent, useMemo } from 'react';
3
3
 
4
+ import { LOCALE_FEATURES } from 'i18n';
5
+ import { selectLocale, useTypedSelector } from 'state';
6
+
4
7
  import SvgFontCss from '../SvgFontCss';
5
8
 
6
9
  import { createPlainTiles, getViewbox } from './lib';
@@ -18,6 +21,8 @@ interface Props {
18
21
  }
19
22
 
20
23
  const PlainTiles: FunctionComponent<Props> = ({ className, color, content, dropShadow, showPoints, style, wave }) => {
24
+ const locale = useTypedSelector(selectLocale);
25
+ const { fontFamily } = LOCALE_FEATURES[locale];
21
26
  const tiles = useMemo(() => createPlainTiles({ color, content, showPoints }), [color, content, showPoints]);
22
27
 
23
28
  return (
@@ -30,13 +35,14 @@ const PlainTiles: FunctionComponent<Props> = ({ className, color, content, dropS
30
35
  viewBox={getViewbox(content)}
31
36
  xmlns="http://www.w3.org/2000/svg"
32
37
  >
33
- <SvgFontCss />
38
+ <SvgFontCss fontFamily={fontFamily} />
34
39
 
35
40
  {tiles.map((tile, index) => (
36
41
  <Tile
37
42
  character={tile.character}
38
43
  className={styles.tile}
39
44
  color={tile.color}
45
+ fontFamily={fontFamily}
40
46
  key={index}
41
47
  points={tile.points}
42
48
  size={tile.size}
@@ -4,6 +4,7 @@ interface Props {
4
4
  character: string;
5
5
  className?: string;
6
6
  color: string;
7
+ fontFamily: string;
7
8
  points?: number;
8
9
  size: number;
9
10
  transform?: string;
@@ -11,13 +12,13 @@ interface Props {
11
12
  y: number;
12
13
  }
13
14
 
14
- const Tile: FunctionComponent<Props> = ({ character, className, color, points, size, transform, x, y }) => (
15
+ const Tile: FunctionComponent<Props> = ({ character, className, color, fontFamily, points, size, transform, x, y }) => (
15
16
  <g className={className} transform={transform}>
16
17
  <rect fill={color} height={size} width={size} x={x} y={y} />
17
18
 
18
19
  <text
19
20
  dominantBaseline="central"
20
- fontFamily="Open Sans"
21
+ fontFamily={fontFamily}
21
22
  fontSize={0.6 * size}
22
23
  fontWeight="bold"
23
24
  textAnchor="middle"
@@ -30,7 +31,7 @@ const Tile: FunctionComponent<Props> = ({ character, className, color, points, s
30
31
  {typeof points === 'number' && (
31
32
  <text
32
33
  dominantBaseline="text-after-edge"
33
- fontFamily="Open Sans"
34
+ fontFamily={fontFamily}
34
35
  fontSize={0.25 * size}
35
36
  fontWeight="bold"
36
37
  textAnchor="end"
@@ -73,11 +73,13 @@ const Rack: FunctionComponent<Props> = ({ className }) => {
73
73
  return createKeyboardNavigation({
74
74
  onArrowLeft: (event) => {
75
75
  event.preventDefault();
76
- changeActiveIndex(-1);
76
+ const direction = document.body.parentElement?.dir || 'ltr';
77
+ changeActiveIndex(direction === 'ltr' ? -1 : 1);
77
78
  },
78
79
  onArrowRight: (event) => {
79
80
  event.preventDefault();
80
- changeActiveIndex(1);
81
+ const direction = document.body.parentElement?.dir || 'ltr';
82
+ changeActiveIndex(direction === 'ltr' ? 1 : -1);
81
83
  },
82
84
  onBackspace: (event) => {
83
85
  event.preventDefault();
@@ -10,6 +10,7 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
10
10
 
11
11
  display: flex;
12
12
  align-items: center;
13
+ gap: var(--spacing--l);
13
14
  padding: var(--spacing--m);
14
15
  transition: var(--transition);
15
16
  cursor: pointer;
@@ -65,5 +66,4 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
65
66
 
66
67
  .content {
67
68
  flex: 1;
68
- margin-left: var(--spacing--l);
69
69
  }
@@ -2,8 +2,9 @@ import { BLANK } from '@scrabble-solver/constants';
2
2
  import classNames from 'classnames';
3
3
  import { FunctionComponent } from 'react';
4
4
 
5
+ import { LOCALE_FEATURES } from 'i18n';
5
6
  import { REMAINING_TILES_TILE_SIZE } from 'parameters';
6
- import { selectCharacterPoints, useTypedSelector } from 'state';
7
+ import { selectCharacterPoints, selectLocale, useTypedSelector } from 'state';
7
8
  import { RemainingTile } from 'types';
8
9
 
9
10
  import Tile from '../Tile';
@@ -15,9 +16,13 @@ interface Props {
15
16
  }
16
17
 
17
18
  const Character: FunctionComponent<Props> = ({ tile }) => {
19
+ const locale = useTypedSelector(selectLocale);
20
+ const { direction } = LOCALE_FEATURES[locale];
18
21
  const { character, count, usedCount } = tile;
19
22
  const remainingCount = count - usedCount;
20
23
  const points = useTypedSelector((state) => selectCharacterPoints(state, character));
24
+ const current = direction === 'ltr' ? remainingCount : count;
25
+ const total = direction === 'ltr' ? count : remainingCount;
21
26
 
22
27
  return (
23
28
  <div
@@ -37,7 +42,7 @@ const Character: FunctionComponent<Props> = ({ tile }) => {
37
42
  size={REMAINING_TILES_TILE_SIZE}
38
43
  />
39
44
  <div className={styles.count}>
40
- {remainingCount} / {count}
45
+ {current.toLocaleString(locale)} / {total.toLocaleString(locale)}
41
46
  </div>
42
47
  </div>
43
48
  );
@@ -1,6 +1,7 @@
1
1
  import { FunctionComponent } from 'react';
2
2
 
3
- import { selectRemainingTilesGroups, useTranslate, useTypedSelector } from 'state';
3
+ import { LOCALE_FEATURES } from 'i18n';
4
+ import { selectLocale, selectRemainingTilesGroups, useTranslate, useTypedSelector } from 'state';
4
5
 
5
6
  import Badge from '../Badge';
6
7
  import Sidebar from '../Sidebar';
@@ -16,29 +17,36 @@ interface Props {
16
17
 
17
18
  const RemainingTiles: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
18
19
  const translate = useTranslate();
20
+ const locale = useTypedSelector(selectLocale);
19
21
  const groups = useTypedSelector(selectRemainingTilesGroups);
22
+ const { direction } = LOCALE_FEATURES[locale];
20
23
 
21
24
  return (
22
25
  <Sidebar className={className} isOpen={isOpen} title={translate('remaining-tiles')} onClose={onClose}>
23
- {groups.map(({ remainingCount, tiles, translationKey, totalCount }) => (
24
- <Sidebar.Section
25
- key={translationKey}
26
- title={
27
- <span className={styles.title}>
28
- <span>{translate(translationKey)}</span>
29
- <Badge className={styles.badge}>
30
- {remainingCount} / {totalCount}
31
- </Badge>
32
- </span>
33
- }
34
- >
35
- <div className={styles.content}>
36
- {tiles.map((tile) => {
37
- return <Character key={tile.character} tile={tile} />;
38
- })}
39
- </div>
40
- </Sidebar.Section>
41
- ))}
26
+ {groups.map(({ remainingCount, tiles, translationKey, totalCount }) => {
27
+ const current = direction === 'ltr' ? remainingCount : totalCount;
28
+ const total = direction === 'ltr' ? totalCount : remainingCount;
29
+
30
+ return (
31
+ <Sidebar.Section
32
+ key={translationKey}
33
+ title={
34
+ <span className={styles.title}>
35
+ <span>{translate(translationKey)}</span>
36
+ <Badge className={styles.badge}>
37
+ {current.toLocaleString(locale)} / {total.toLocaleString(locale)}
38
+ </Badge>
39
+ </span>
40
+ }
41
+ >
42
+ <div className={styles.content}>
43
+ {tiles.map((tile) => {
44
+ return <Character key={tile.character} tile={tile} />;
45
+ })}
46
+ </div>
47
+ </Sidebar.Section>
48
+ );
49
+ })}
42
50
  </Sidebar>
43
51
  );
44
52
  };
@@ -1,7 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
- import { useTranslate } from 'state';
4
+ import { selectLocale, useTranslate, useTypedSelector } from 'state';
5
5
  import { TranslationKey } from 'types';
6
6
 
7
7
  import { useTooltip } from '../Tooltip';
@@ -17,11 +17,13 @@ interface Props {
17
17
 
18
18
  const Cell: FunctionComponent<Props> = ({ className, translationKey, tooltip, value }) => {
19
19
  const translate = useTranslate();
20
- const triggerProps = useTooltip(`${translate(translationKey)}: ${tooltip || value}`);
20
+ const locale = useTypedSelector(selectLocale);
21
+ const formattedValue = value.toLocaleString(locale);
22
+ const triggerProps = useTooltip(`${translate(translationKey)}: ${tooltip || formattedValue}`);
21
23
 
22
24
  return (
23
25
  <span className={classNames(styles.cell, className)} {...triggerProps}>
24
- {value}
26
+ {formattedValue}
25
27
  </span>
26
28
  );
27
29
  };
@@ -1,7 +1,8 @@
1
1
  import { CSSProperties, ReactElement } from 'react';
2
2
  import { useDispatch } from 'react-redux';
3
3
 
4
- import { resultsSlice, selectSortedFilteredResults, useTypedSelector } from 'state';
4
+ import { LOCALE_FEATURES } from 'i18n';
5
+ import { resultsSlice, selectLocale, selectSortedFilteredResults, useTypedSelector } from 'state';
5
6
 
6
7
  import Cell from './Cell';
7
8
  import styles from './Results.module.scss';
@@ -13,6 +14,8 @@ interface Props {
13
14
 
14
15
  const Result = ({ index, style }: Props): ReactElement => {
15
16
  const dispatch = useDispatch();
17
+ const locale = useTypedSelector(selectLocale);
18
+ const { consonants, vowels } = LOCALE_FEATURES[locale];
16
19
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17
20
  const results = useTypedSelector(selectSortedFilteredResults)!;
18
21
  const result = results[index];
@@ -56,8 +59,10 @@ const Result = ({ index, style }: Props): ReactElement => {
56
59
  value={`${result.word.toLocaleUpperCase()}${otherWords.length > 0 ? ` (${otherWords})` : ''}`}
57
60
  />
58
61
  <Cell className={styles.stat} translationKey="common.tiles" value={result.tilesCount} />
59
- <Cell className={styles.stat} translationKey="common.consonants" value={result.consonantsCount} />
60
- <Cell className={styles.stat} translationKey="common.vowels" value={result.vowelsCount} />
62
+ {consonants && (
63
+ <Cell className={styles.stat} translationKey="common.consonants" value={result.consonantsCount} />
64
+ )}
65
+ {vowels && <Cell className={styles.stat} translationKey="common.vowels" value={result.vowelsCount} />}
61
66
  <Cell className={styles.stat} translationKey="common.blanks" value={result.blanksCount} />
62
67
  <Cell
63
68
  className={styles.stat}