scrabble-solver 2.9.2 → 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 (216) hide show
  1. package/.prettierignore +2 -0
  2. package/README.md +41 -11
  3. package/lerna.json +1 -1
  4. package/package.json +2 -2
  5. package/packages/configs/build/literaki/literaki.d.ts +1 -0
  6. package/packages/configs/build/literaki/literaki.js +1 -0
  7. package/packages/configs/build/literaki/tiles/index.d.ts +1 -0
  8. package/packages/configs/build/literaki/tiles/index.js +3 -1
  9. package/packages/configs/build/literaki/tiles/tilesFa.d.ts +6 -0
  10. package/packages/configs/build/literaki/tiles/tilesFa.js +37 -0
  11. package/packages/configs/build/scrabble/scrabble.d.ts +1 -0
  12. package/packages/configs/build/scrabble/scrabble.js +1 -0
  13. package/packages/configs/build/scrabble/tiles/index.d.ts +1 -0
  14. package/packages/configs/build/scrabble/tiles/index.js +3 -1
  15. package/packages/configs/build/scrabble/tiles/tilesFa.d.ts +6 -0
  16. package/packages/configs/build/scrabble/tiles/tilesFa.js +37 -0
  17. package/packages/configs/package.json +3 -3
  18. package/packages/configs/src/literaki/literaki.ts +2 -1
  19. package/packages/configs/src/literaki/tiles/index.ts +1 -0
  20. package/packages/configs/src/literaki/tiles/tilesFa.ts +36 -0
  21. package/packages/configs/src/scrabble/scrabble.ts +2 -1
  22. package/packages/configs/src/scrabble/tiles/index.ts +1 -0
  23. package/packages/configs/src/scrabble/tiles/tilesFa.ts +36 -0
  24. package/packages/constants/package.json +1 -1
  25. package/packages/dictionaries/package-lock.json +2 -2
  26. package/packages/dictionaries/package.json +4 -4
  27. package/packages/logger/package-lock.json +2 -2
  28. package/packages/logger/package.json +1 -1
  29. package/packages/logger/scripts/stats.js +1 -1
  30. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  31. package/packages/scrabble-solver/.next/build-manifest.json +11 -11
  32. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  33. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  34. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  35. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  36. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  37. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  38. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  39. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  40. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  41. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  42. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  43. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  44. package/packages/scrabble-solver/.next/server/chunks/{429.js → 131.js} +2 -3652
  45. package/packages/scrabble-solver/.next/server/chunks/413.js +367 -185
  46. package/packages/scrabble-solver/.next/server/chunks/44.js +71 -45
  47. package/packages/scrabble-solver/.next/server/chunks/515.js +1135 -504
  48. package/packages/scrabble-solver/.next/server/chunks/911.js +96 -2
  49. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  50. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  51. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  52. package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
  53. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  54. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  55. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +48 -10
  56. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  57. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -8
  58. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  59. package/packages/scrabble-solver/.next/server/pages/api/solve.js +62 -49
  60. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  61. package/packages/scrabble-solver/.next/server/pages/api/verify.js +2 -9
  62. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  63. package/packages/scrabble-solver/.next/server/pages/index.html +4 -8
  64. package/packages/scrabble-solver/.next/server/pages/index.js +43 -36
  65. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  66. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  67. package/packages/scrabble-solver/.next/server/pages-manifest.json +2 -2
  68. package/packages/scrabble-solver/.next/static/chunks/368-d423e70be6c0c473.js +1 -0
  69. package/packages/scrabble-solver/.next/static/chunks/pages/{404-7082923654d5996f.js → 404-932294135c3206dd.js} +1 -1
  70. package/packages/scrabble-solver/.next/static/chunks/pages/_app-3f5508a5f544d9eb.js +1 -0
  71. package/packages/scrabble-solver/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +1 -0
  72. package/packages/scrabble-solver/.next/static/css/6b1833fd19d3a74a.css +1 -0
  73. package/packages/scrabble-solver/.next/static/css/a6154e4ca046ca13.css +1 -0
  74. package/packages/scrabble-solver/.next/static/css/bad53af6f8616677.css +1 -0
  75. package/packages/scrabble-solver/.next/static/vscqn7BEtAxJteWSwNnas/_buildManifest.js +1 -0
  76. package/packages/scrabble-solver/.next/static/{Ntg-ilwD7GqTIFwRpSaTQ → vscqn7BEtAxJteWSwNnas}/_ssgManifest.js +0 -0
  77. package/packages/scrabble-solver/.next/trace +52 -52
  78. package/packages/scrabble-solver/package-lock.json +2 -2
  79. package/packages/scrabble-solver/package.json +8 -8
  80. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +45 -9
  81. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +4 -2
  82. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +8 -1
  83. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +6 -2
  84. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +13 -2
  85. package/packages/scrabble-solver/src/components/Logo/Logo.svg +62 -0
  86. package/packages/scrabble-solver/src/components/Logo/index.ts +1 -1
  87. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss +8 -7
  88. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +35 -28
  89. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +7 -1
  90. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +4 -3
  91. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +4 -2
  92. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss +1 -1
  93. package/packages/scrabble-solver/src/components/RemainingTiles/Character.tsx +7 -2
  94. package/packages/scrabble-solver/src/components/RemainingTiles/RemainingTiles.tsx +28 -20
  95. package/packages/scrabble-solver/src/components/Results/Cell.tsx +5 -3
  96. package/packages/scrabble-solver/src/components/Results/Result.tsx +8 -3
  97. package/packages/scrabble-solver/src/components/Results/Results.module.scss +31 -9
  98. package/packages/scrabble-solver/src/components/Results/Results.tsx +6 -2
  99. package/packages/scrabble-solver/src/components/Results/getColumns.ts +58 -0
  100. package/packages/scrabble-solver/src/components/Settings/components/ConfigSetting/ConfigSetting.module.scss +1 -0
  101. package/packages/scrabble-solver/src/components/Settings/components/LocaleSetting/LocaleSetting.module.scss +12 -1
  102. package/packages/scrabble-solver/src/components/Settings/components/LocaleSetting/LocaleSetting.tsx +1 -1
  103. package/packages/scrabble-solver/src/components/Settings/components/LocaleSetting/options.ts +7 -1
  104. package/packages/scrabble-solver/src/components/Sidebar/Sidebar.module.scss +42 -8
  105. package/packages/scrabble-solver/src/components/SvgFontCss/SvgFontCss.tsx +8 -7
  106. package/packages/scrabble-solver/src/components/SvgFontCss/createCss.ts +11 -0
  107. package/packages/scrabble-solver/src/components/SvgFontCss/createStyle.ts +9 -0
  108. package/packages/scrabble-solver/src/components/SvgFontCss/createSvg.ts +10 -0
  109. package/packages/scrabble-solver/src/components/SvgFontFix/SvgFontFix.module.scss +5 -0
  110. package/packages/scrabble-solver/src/components/SvgFontFix/SvgFontFix.tsx +21 -0
  111. package/packages/scrabble-solver/src/components/SvgFontFix/index.ts +1 -0
  112. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +10 -2
  113. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +4 -0
  114. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +3 -1
  115. package/packages/scrabble-solver/src/components/Words/Words.tsx +4 -3
  116. package/packages/scrabble-solver/src/components/index.ts +1 -0
  117. package/packages/scrabble-solver/src/hooks/index.ts +2 -0
  118. package/packages/scrabble-solver/src/hooks/useDirection.ts +22 -0
  119. package/packages/scrabble-solver/src/hooks/useLanguage.ts +22 -0
  120. package/packages/scrabble-solver/src/i18n/constants.ts +53 -0
  121. package/packages/scrabble-solver/src/i18n/fa.json +56 -0
  122. package/packages/scrabble-solver/src/i18n/i18n.ts +22 -0
  123. package/packages/scrabble-solver/src/i18n/index.ts +2 -20
  124. package/packages/scrabble-solver/src/icons/FlagFa.svg +95 -0
  125. package/packages/scrabble-solver/src/icons/index.ts +1 -0
  126. package/packages/scrabble-solver/src/lib/createComparator.ts +22 -0
  127. package/packages/scrabble-solver/src/lib/createKeyComparator.ts +4 -2
  128. package/packages/scrabble-solver/src/lib/createStringComparator.ts +5 -0
  129. package/packages/scrabble-solver/src/lib/detectLocale.ts +4 -0
  130. package/packages/scrabble-solver/src/lib/getRemainingTiles.ts +4 -2
  131. package/packages/scrabble-solver/src/lib/getRemainingTilesGroups.ts +48 -23
  132. package/packages/scrabble-solver/src/lib/index.ts +2 -2
  133. package/packages/scrabble-solver/src/lib/sortResults.ts +11 -9
  134. package/packages/scrabble-solver/src/lib/unorderedArraysEqual.ts +3 -2
  135. package/packages/scrabble-solver/src/pages/api/verify.ts +1 -1
  136. package/packages/scrabble-solver/src/pages/index.module.scss +7 -18
  137. package/packages/scrabble-solver/src/pages/index.tsx +10 -2
  138. package/packages/scrabble-solver/src/service-worker/routeVerifyRequests.ts +1 -1
  139. package/packages/scrabble-solver/src/state/selectors.ts +5 -5
  140. package/packages/scrabble-solver/src/styles/global.scss +6 -1
  141. package/packages/solver/package-lock.json +2 -2
  142. package/packages/solver/package.json +5 -5
  143. package/packages/types/build/Locale.d.ts +1 -0
  144. package/packages/types/build/Locale.js +1 -0
  145. package/packages/types/build/Result.d.ts +0 -1
  146. package/packages/types/build/Result.js +0 -5
  147. package/packages/types/package.json +2 -2
  148. package/packages/types/src/Locale.ts +1 -0
  149. package/packages/types/src/Result.ts +0 -11
  150. package/packages/word-definitions/build/crawl/crawl.js +2 -0
  151. package/packages/word-definitions/build/crawl/crawlFarsi.d.ts +2 -0
  152. package/packages/word-definitions/build/crawl/crawlFarsi.js +11 -0
  153. package/packages/word-definitions/build/lib/normalizeDefinition.js +2 -1
  154. package/packages/word-definitions/build/parse/index.d.ts +1 -0
  155. package/packages/word-definitions/build/parse/index.js +3 -1
  156. package/packages/word-definitions/build/parse/parse.js +2 -0
  157. package/packages/word-definitions/build/parse/parseFarsi.d.ts +3 -0
  158. package/packages/word-definitions/build/parse/parseFarsi.js +13 -0
  159. package/packages/word-definitions/package-lock.json +2 -2
  160. package/packages/word-definitions/package.json +2 -2
  161. package/packages/word-definitions/src/crawl/crawl.ts +2 -0
  162. package/packages/word-definitions/src/crawl/crawlFarsi.ts +11 -0
  163. package/packages/word-definitions/src/lib/normalizeDefinition.ts +2 -1
  164. package/packages/word-definitions/src/parse/index.ts +1 -0
  165. package/packages/word-definitions/src/parse/parse.ts +2 -0
  166. package/packages/word-definitions/src/parse/parseFarsi.ts +17 -0
  167. package/packages/word-lists/build/getDeDeWordList.js +2 -1
  168. package/packages/word-lists/build/getEnGbWordList.js +2 -1
  169. package/packages/word-lists/build/getEnUsWordList.js +2 -1
  170. package/packages/word-lists/build/getEsEsWordList.js +2 -1
  171. package/packages/word-lists/build/getFaIrWordList.d.ts +2 -0
  172. package/packages/word-lists/build/getFaIrWordList.js +9 -0
  173. package/packages/word-lists/build/getFrFrWordList.js +2 -1
  174. package/packages/word-lists/build/getPlPlWordList.js +9 -9
  175. package/packages/word-lists/build/getWordList.js +2 -0
  176. package/packages/word-lists/build/lib/downloadFile.js +5 -5
  177. package/packages/word-lists/build/lib/extractWords.d.ts +1 -1
  178. package/packages/word-lists/build/lib/extractWords.js +10 -7
  179. package/packages/word-lists/build/lib/findFirstWordIndex.d.ts +1 -1
  180. package/packages/word-lists/build/lib/findFirstWordIndex.js +2 -2
  181. package/packages/word-lists/build/lib/getTempFilepath.d.ts +2 -0
  182. package/packages/word-lists/build/lib/getTempFilepath.js +14 -0
  183. package/packages/word-lists/build/lib/getTxtWordList.d.ts +1 -1
  184. package/packages/word-lists/build/lib/getTxtWordList.js +5 -5
  185. package/packages/word-lists/build/lib/index.d.ts +1 -1
  186. package/packages/word-lists/build/lib/index.js +3 -3
  187. package/packages/word-lists/package-lock.json +2 -32
  188. package/packages/word-lists/package.json +2 -3
  189. package/packages/word-lists/src/getDeDeWordList.ts +3 -1
  190. package/packages/word-lists/src/getEnGbWordList.ts +3 -1
  191. package/packages/word-lists/src/getEnUsWordList.ts +3 -1
  192. package/packages/word-lists/src/getEsEsWordList.ts +3 -1
  193. package/packages/word-lists/src/getFaIrWordList.ts +11 -0
  194. package/packages/word-lists/src/getFrFrWordList.ts +3 -1
  195. package/packages/word-lists/src/getPlPlWordList.ts +7 -7
  196. package/packages/word-lists/src/getWordList.ts +2 -0
  197. package/packages/word-lists/src/lib/downloadFile.ts +5 -5
  198. package/packages/word-lists/src/lib/extractWords.ts +13 -7
  199. package/packages/word-lists/src/lib/findFirstWordIndex.ts +2 -2
  200. package/packages/word-lists/src/lib/getTempFilepath.ts +13 -0
  201. package/packages/word-lists/src/lib/getTxtWordList.ts +3 -3
  202. package/packages/word-lists/src/lib/index.ts +1 -1
  203. package/img/logo.svg +0 -76
  204. package/packages/scrabble-solver/.next/static/Ntg-ilwD7GqTIFwRpSaTQ/_buildManifest.js +0 -1
  205. package/packages/scrabble-solver/.next/static/chunks/317-5e5334962dd7c681.js +0 -1
  206. package/packages/scrabble-solver/.next/static/chunks/pages/_app-183f598b1d4d480b.js +0 -1
  207. package/packages/scrabble-solver/.next/static/chunks/pages/index-b1ffeaddd9fb64b5.js +0 -1
  208. package/packages/scrabble-solver/.next/static/css/751e8a14776d05d8.css +0 -1
  209. package/packages/scrabble-solver/.next/static/css/ad2a08918868cad8.css +0 -1
  210. package/packages/scrabble-solver/.next/static/css/e8de67ad5ea35427.css +0 -1
  211. package/packages/scrabble-solver/src/components/Results/constants.ts +0 -42
  212. package/packages/scrabble-solver/src/lib/comparator.ts +0 -16
  213. package/packages/scrabble-solver/src/lib/stringComparator.ts +0 -5
  214. package/packages/word-lists/build/lib/getTempFilename.d.ts +0 -2
  215. package/packages/word-lists/build/lib/getTempFilename.js +0 -10
  216. package/packages/word-lists/src/lib/getTempFilename.ts +0 -7
@@ -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}
@@ -54,6 +54,10 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
54
54
 
55
55
  flex: 0 1 auto;
56
56
  text-align: left;
57
+
58
+ [dir='rtl'] & {
59
+ text-align: right;
60
+ }
57
61
  }
58
62
 
59
63
  .result {
@@ -78,6 +82,10 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
78
82
  align-items: center;
79
83
  justify-content: space-between;
80
84
 
85
+ [dir='rtl'] & {
86
+ flex-direction: row-reverse;
87
+ }
88
+
81
89
  .word {
82
90
  @include ellipsis;
83
91
  }
@@ -88,17 +96,32 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
88
96
  align-items: center;
89
97
  justify-content: center;
90
98
  padding: var(--spacing--s);
99
+ gap: var(--spacing--s);
91
100
 
92
101
  .result &:first-child,
93
102
  .headerButton:first-child & {
94
- padding-left: $row-padding-horizontal;
95
103
  justify-content: flex-start;
96
- text-align: left;
104
+
105
+ [dir='ltr'] & {
106
+ padding-left: $row-padding-horizontal;
107
+ text-align: left;
108
+ }
109
+
110
+ [dir='rtl'] & {
111
+ padding-right: $row-padding-horizontal;
112
+ text-align: right;
113
+ }
97
114
  }
98
115
 
99
116
  .result &:last-child,
100
117
  .headerButton:last-child & {
101
- padding-right: $row-padding-horizontal;
118
+ [dir='ltr'] & {
119
+ padding-right: $row-padding-horizontal;
120
+ }
121
+
122
+ [dir='rtl'] & {
123
+ padding-left: $row-padding-horizontal;
124
+ }
102
125
  }
103
126
 
104
127
  &:last-child {
@@ -107,10 +130,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
107
130
  }
108
131
 
109
132
  .word {
110
- $width: 180px;
111
-
112
- flex: 0 0 $width;
113
- max-width: $width;
133
+ flex: 1 0 180px;
114
134
  text-transform: uppercase;
115
135
  }
116
136
 
@@ -123,7 +143,10 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
123
143
  }
124
144
 
125
145
  .points {
126
- flex: 1 0 80px;
146
+ $width: 80px;
147
+
148
+ flex: 1 0 $width;
149
+ max-width: $width;
127
150
  font-weight: bold;
128
151
  }
129
152
 
@@ -143,7 +166,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
143
166
  flex: 0 0 auto;
144
167
  width: $size;
145
168
  height: $size;
146
- margin-left: var(--spacing--s);
147
169
  }
148
170
 
149
171
  .input {
@@ -2,10 +2,12 @@ import classNames from 'classnames';
2
2
  import { FunctionComponent, useLayoutEffect, useRef } from 'react';
3
3
  import { FixedSizeList } from 'react-window';
4
4
 
5
+ import { LOCALE_FEATURES } from 'i18n';
5
6
  import { RESULTS_HEADER_HEIGHT, RESULTS_INPUT_HEIGHT, RESULTS_ITEM_HEIGHT } from 'parameters';
6
7
  import {
7
8
  selectAreResultsOutdated,
8
9
  selectIsLoading,
10
+ selectLocale,
9
11
  selectSolveError,
10
12
  selectSortedFilteredResults,
11
13
  selectSortedResults,
@@ -17,7 +19,7 @@ import EmptyState from '../EmptyState';
17
19
  import Loading from '../Loading';
18
20
  import ResultsInput from '../ResultsInput';
19
21
 
20
- import { COLUMNS } from './constants';
22
+ import getColumns from './getColumns';
21
23
  import HeaderButton from './HeaderButton';
22
24
  import Result from './Result';
23
25
  import styles from './Results.module.scss';
@@ -29,6 +31,7 @@ interface Props {
29
31
  }
30
32
 
31
33
  const Results: FunctionComponent<Props> = ({ height, width }) => {
34
+ const locale = useTypedSelector(selectLocale);
32
35
  const translate = useTranslate();
33
36
  const allResults = useTypedSelector(selectSortedResults);
34
37
  const results = useTypedSelector(selectSortedFilteredResults);
@@ -36,6 +39,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
36
39
  const isOutdated = useTypedSelector(selectAreResultsOutdated);
37
40
  const error = useTypedSelector(selectSolveError);
38
41
  const listRef = useRef<HTMLElement>();
42
+ const columns = getColumns(LOCALE_FEATURES[locale]);
39
43
 
40
44
  useLayoutEffect(() => {
41
45
  if (listRef.current) {
@@ -46,7 +50,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
46
50
  return (
47
51
  <div className={styles.results}>
48
52
  <div className={styles.header}>
49
- {COLUMNS.map((column) => (
53
+ {columns.map((column) => (
50
54
  <HeaderButton column={column} key={column.id} />
51
55
  ))}
52
56
  </div>
@@ -0,0 +1,58 @@
1
+ import { ResultColumn } from 'types';
2
+
3
+ import styles from './Results.module.scss';
4
+ import { Column } from './types';
5
+
6
+ const getColumns = (options: { consonants: boolean; vowels: boolean }): Column[] => {
7
+ const { consonants, vowels } = options;
8
+ const columns: Column[] = [
9
+ {
10
+ className: styles.word,
11
+ id: ResultColumn.Word,
12
+ translationKey: 'common.word',
13
+ },
14
+ {
15
+ className: styles.stat,
16
+ id: ResultColumn.TilesCount,
17
+ translationKey: 'common.tiles',
18
+ },
19
+ ];
20
+
21
+ if (consonants) {
22
+ columns.push({
23
+ className: styles.stat,
24
+ id: ResultColumn.ConsonantsCount,
25
+ translationKey: 'common.consonants',
26
+ });
27
+ }
28
+
29
+ if (vowels) {
30
+ columns.push({
31
+ className: styles.stat,
32
+ id: ResultColumn.VowelsCount,
33
+ translationKey: 'common.vowels',
34
+ });
35
+ }
36
+
37
+ columns.push(
38
+ {
39
+ className: styles.stat,
40
+ id: ResultColumn.BlanksCount,
41
+ translationKey: 'common.blanks',
42
+ },
43
+ {
44
+ className: styles.stat,
45
+ id: ResultColumn.WordsCount,
46
+ translationKey: 'common.words',
47
+ },
48
+ {
49
+ className: styles.points,
50
+ id: ResultColumn.Points,
51
+ translationKey: 'common.points',
52
+ },
53
+ );
54
+
55
+ return columns;
56
+ };
57
+
58
+ export default getColumns;
@@ -9,4 +9,5 @@
9
9
  .label {
10
10
  display: flex;
11
11
  align-items: center;
12
+ font-family: 'Open Sans', sans-serif;
12
13
  }
@@ -18,13 +18,18 @@
18
18
  .label {
19
19
  display: flex;
20
20
  align-items: center;
21
+ gap: var(--spacing--l);
22
+ font-family: 'Open Sans', sans-serif;
23
+
24
+ &.fa {
25
+ font-family: 'Vazirmatn', 'Open Sans', sans-serif;
26
+ }
21
27
  }
22
28
 
23
29
  .flag {
24
30
  $height: 32px;
25
31
 
26
32
  height: $height;
27
- margin-right: var(--spacing--l);
28
33
  box-shadow: var(--box-shadow--null);
29
34
  transition: var(--transition);
30
35
 
@@ -40,6 +45,12 @@
40
45
  width: $height * $aspect-ratio;
41
46
  }
42
47
 
48
+ &.fa {
49
+ $aspect-ratio: 1.75;
50
+
51
+ width: $height * $aspect-ratio;
52
+ }
53
+
43
54
  &.gb {
44
55
  $aspect-ratio: 2;
45
56
 
@@ -39,7 +39,7 @@ const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
39
39
  value={option.value}
40
40
  onChange={handleChange}
41
41
  >
42
- <span className={styles.label}>
42
+ <span className={classNames(styles.label, option.className)}>
43
43
  <Icon className={classNames(styles.flag, option.className)} />
44
44
 
45
45
  <span>{option.label}</span>
@@ -1,7 +1,7 @@
1
1
  import { Locale } from '@scrabble-solver/types';
2
2
  import { FunctionComponent, SVGAttributes } from 'react';
3
3
 
4
- import { FlagEs, FlagFr, FlagGb, FlagPl, FlagUs, FlagDe } from 'icons';
4
+ import { FlagEs, FlagFa, FlagFr, FlagGb, FlagPl, FlagUs, FlagDe } from 'icons';
5
5
 
6
6
  import styles from './LocaleSetting.module.scss';
7
7
 
@@ -25,6 +25,12 @@ const options: Option[] = [
25
25
  label: 'English (US)',
26
26
  value: Locale.EN_US,
27
27
  },
28
+ {
29
+ className: styles.fa,
30
+ Icon: FlagFa,
31
+ label: 'فارسی',
32
+ value: Locale.FA_IR,
33
+ },
28
34
  {
29
35
  className: styles.fr,
30
36
  Icon: FlagFr,
@@ -3,10 +3,21 @@
3
3
  $z-index: 100;
4
4
  $width: 370px;
5
5
 
6
+ .modal,
7
+ .closeButton {
8
+ top: 0;
9
+
10
+ [dir='ltr'] & {
11
+ right: 0;
12
+ }
13
+
14
+ [dir='rtl'] & {
15
+ left: 0;
16
+ }
17
+ }
18
+
6
19
  .modal {
7
20
  position: fixed;
8
- top: 0;
9
- right: 0;
10
21
  z-index: $z-index;
11
22
  height: 100vh;
12
23
  width: $width;
@@ -21,15 +32,25 @@ $width: 370px;
21
32
 
22
33
  .afterOpen {
23
34
  .sidebar {
24
- transform: translateX(0);
25
- opacity: 1;
35
+ [dir='ltr'] &,
36
+ [dir='rtl'] & {
37
+ transform: translateX(0);
38
+ opacity: 1;
39
+ }
26
40
  }
27
41
  }
28
42
 
29
43
  .beforeClose {
30
44
  .sidebar {
31
- transform: translateX($width);
32
45
  opacity: 0;
46
+
47
+ [dir='ltr'] & {
48
+ transform: translateX($width);
49
+ }
50
+
51
+ [dir='rtl'] & {
52
+ transform: translateX(-$width);
53
+ }
33
54
  }
34
55
  }
35
56
 
@@ -47,6 +68,14 @@ $width: 370px;
47
68
  transition-duration: var(--transition--duration--long);
48
69
  transform: translateX($width);
49
70
  opacity: 0;
71
+
72
+ [dir='ltr'] & {
73
+ transform: translateX($width);
74
+ }
75
+
76
+ [dir='rtl'] & {
77
+ transform: translateX(-$width);
78
+ }
50
79
  }
51
80
 
52
81
  .header {
@@ -56,15 +85,20 @@ $width: 370px;
56
85
  }
57
86
 
58
87
  .title {
59
- padding-right: calc(var(--square-button-size) + var(--spacing--s));
60
88
  font-size: var(--font--size--h1);
61
89
  font-weight: 300;
90
+
91
+ [dir='ltr'] & {
92
+ padding-right: calc(var(--square-button-size) + var(--spacing--s));
93
+ }
94
+
95
+ [dir='rtl'] & {
96
+ padding-left: calc(var(--square-button-size) + var(--spacing--s));
97
+ }
62
98
  }
63
99
 
64
100
  .closeButton {
65
101
  position: absolute !important;
66
- top: 0;
67
- right: 0;
68
102
  z-index: $z-index + 1;
69
103
  margin: var(--spacing--l);
70
104
  }
@@ -1,13 +1,14 @@
1
1
  import { FunctionComponent } from 'react';
2
2
 
3
- // eslint-disable-next-line max-len
4
- const CSS = `@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Open+Sans:wght@400;700&family=Roboto+Mono&display=swap');
3
+ import createCss from './createCss';
5
4
 
6
- text {
7
- font-family: 'Open Sans';
8
- }`;
5
+ interface Props {
6
+ fontFamily: string;
7
+ }
9
8
 
10
- // eslint-disable-next-line react/no-danger
11
- const SvgFontCss: FunctionComponent = () => <style type="text/css" dangerouslySetInnerHTML={{ __html: CSS }} />;
9
+ const SvgFontCss: FunctionComponent<Props> = ({ fontFamily }) => {
10
+ // eslint-disable-next-line react/no-danger
11
+ return <style type="text/css" dangerouslySetInnerHTML={{ __html: createCss(fontFamily) }} />;
12
+ };
12
13
 
13
14
  export default SvgFontCss;
@@ -0,0 +1,11 @@
1
+ const importUrl =
2
+ // eslint-disable-next-line max-len
3
+ 'https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Open+Sans:wght@400;700&family=Vazirmatn:wght@300;400;700&family=Roboto+Mono&display=swap';
4
+
5
+ const createCss = (fontFamily: string): string => `@import url('${importUrl}');
6
+
7
+ text {
8
+ font-family: '${fontFamily}';
9
+ }`;
10
+
11
+ export default createCss;
@@ -0,0 +1,9 @@
1
+ import createCss from './createCss';
2
+
3
+ const createStyle = (fontFamily: string) => {
4
+ const style = document.createElement('style');
5
+ style.innerHTML = createCss(fontFamily);
6
+ return style;
7
+ };
8
+
9
+ export default createStyle;
@@ -0,0 +1,10 @@
1
+ import createStyle from './createStyle';
2
+
3
+ const createSvg = (fontFamily: string) => {
4
+ const svg = document.createElement('svg');
5
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
6
+ svg.appendChild(createStyle(fontFamily));
7
+ return svg;
8
+ };
9
+
10
+ export default createSvg;
@@ -0,0 +1,5 @@
1
+ .svgFontFix {
2
+ width: 0;
3
+ height: 0;
4
+ position: absolute;
5
+ }
@@ -0,0 +1,21 @@
1
+ import { FunctionComponent } from 'react';
2
+
3
+ import { LOCALE_FEATURES } from 'i18n';
4
+
5
+ import SvgFontCss from '../SvgFontCss';
6
+
7
+ import styles from './SvgFontFix.module.scss';
8
+
9
+ const SvgFontFix: FunctionComponent = () => {
10
+ const fontFamilies = Array.from(new Set(Object.values(LOCALE_FEATURES).map(({ fontFamily }) => fontFamily)));
11
+
12
+ return (
13
+ <svg className={styles.svgFontFix} xmlns="http://www.w3.org/2000/svg">
14
+ {fontFamilies.map((fontFamily) => (
15
+ <SvgFontCss fontFamily={fontFamily} key={fontFamily} />
16
+ ))}
17
+ </svg>
18
+ );
19
+ };
20
+
21
+ export default SvgFontFix;
@@ -0,0 +1 @@
1
+ export { default } from './SvgFontFix';
@@ -73,10 +73,18 @@
73
73
 
74
74
  .points {
75
75
  position: absolute;
76
- right: 9%;
77
- bottom: 1%;
78
76
  font-weight: bold;
79
77
  user-select: none;
80
78
  pointer-events: none;
81
79
  letter-spacing: -1px;
80
+
81
+ [dir='ltr'] & {
82
+ bottom: 1%;
83
+ right: 9%;
84
+ }
85
+
86
+ [dir='rtl'] & {
87
+ top: 1%;
88
+ left: 9%;
89
+ }
82
90
  }
@@ -11,6 +11,7 @@ import {
11
11
  } from 'react';
12
12
 
13
13
  import { getTileSizes, noop } from 'lib';
14
+ import { selectLocale, useTypedSelector } from 'state';
14
15
 
15
16
  import TilePure from './TilePure';
16
17
 
@@ -49,6 +50,7 @@ const Tile: FunctionComponent<Props> = ({
49
50
  onFocus = noop,
50
51
  onKeyDown = noop,
51
52
  }) => {
53
+ const locale = useTypedSelector(selectLocale);
52
54
  const { pointsFontSize, tileFontSize, tileSize } = getTileSizes(size);
53
55
  const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
54
56
  const inputStyle = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
@@ -56,6 +58,7 @@ const Tile: FunctionComponent<Props> = ({
56
58
  const inputRef = useMemo<RefObject<HTMLInputElement>>(() => ref || createRef(), [ref]);
57
59
  const isEmpty = !character || character === EMPTY_CELL;
58
60
  const canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined';
61
+ const pointsFormatted = typeof points === 'number' ? points.toLocaleString(locale) : '';
59
62
 
60
63
  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
61
64
  inputRef.current?.select();
@@ -81,6 +84,7 @@ const Tile: FunctionComponent<Props> = ({
81
84
  isBlank={isBlank}
82
85
  placeholder={placeholder}
83
86
  points={points}
87
+ pointsFormatted={pointsFormatted}
84
88
  pointsStyle={pointsStyle}
85
89
  raised={raised}
86
90
  style={style}
@@ -23,6 +23,7 @@ interface Props {
23
23
  isBlank?: boolean;
24
24
  placeholder?: string;
25
25
  points?: number;
26
+ pointsFormatted?: string;
26
27
  pointsStyle?: CSSProperties;
27
28
  raised?: boolean;
28
29
  style?: CSSProperties;
@@ -44,6 +45,7 @@ const TilePure: FunctionComponent<Props> = ({
44
45
  isBlank,
45
46
  placeholder,
46
47
  points,
48
+ pointsFormatted,
47
49
  pointsStyle,
48
50
  raised,
49
51
  style,
@@ -85,7 +87,7 @@ const TilePure: FunctionComponent<Props> = ({
85
87
 
86
88
  {canShowPoints && (
87
89
  <span className={styles.points} style={pointsStyle}>
88
- {points}
90
+ {pointsFormatted}
89
91
  </span>
90
92
  )}
91
93
  </div>
@@ -2,7 +2,7 @@ import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
4
  import { Check, Cross } from 'icons';
5
- import { selectVerify, useTranslate, useTypedSelector } from 'state';
5
+ import { selectLocale, selectVerify, useTranslate, useTypedSelector } from 'state';
6
6
 
7
7
  import Badge from '../Badge';
8
8
  import Sidebar from '../Sidebar';
@@ -17,6 +17,7 @@ interface Props {
17
17
 
18
18
  const Words: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
19
19
  const translate = useTranslate();
20
+ const locale = useTypedSelector(selectLocale);
20
21
  const { invalidWords, validWords } = useTypedSelector(selectVerify);
21
22
 
22
23
  return (
@@ -25,7 +26,7 @@ const Words: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
25
26
  title={
26
27
  <span className={styles.title}>
27
28
  <span>{translate('words.invalid')}</span>
28
- <Badge className={styles.badge}>{invalidWords.length}</Badge>
29
+ <Badge className={styles.badge}>{invalidWords.length.toLocaleString(locale)}</Badge>
29
30
  </span>
30
31
  }
31
32
  >
@@ -40,7 +41,7 @@ const Words: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
40
41
  title={
41
42
  <span className={styles.title}>
42
43
  <span>{translate('words.valid')}</span>
43
- <Badge className={styles.badge}>{validWords.length}</Badge>
44
+ <Badge className={styles.badge}>{validWords.length.toLocaleString(locale)}</Badge>
44
45
  </span>
45
46
  }
46
47
  >
@@ -23,6 +23,7 @@ export { default as Sidebar } from './Sidebar';
23
23
  export { default as Splash } from './Splash';
24
24
  export { default as SquareButton } from './SquareButton';
25
25
  export { default as SvgFontCss } from './SvgFontCss';
26
+ export { default as SvgFontFix } from './SvgFontFix';
26
27
  export { default as Tile } from './Tile';
27
28
  export { useTooltip } from './Tooltip';
28
29
  export { default as Well } from './Well';
@@ -1,4 +1,6 @@
1
+ export { default as useDirection } from './useDirection';
1
2
  export { default as useIsTablet } from './useIsTablet';
3
+ export { default as useLanguage } from './useLanguage';
2
4
  export { default as useLocalStorage } from './useLocalStorage';
3
5
  export { default as usePortal } from './usePortal';
4
6
  export { default as useUniqueId } from './useUniqueId';
@@ -0,0 +1,22 @@
1
+ import { useLayoutEffect } from 'react';
2
+
3
+ import { noop } from 'lib';
4
+
5
+ const useDirection = (direction: 'ltr' | 'rtl') => {
6
+ useLayoutEffect(() => {
7
+ const html = document.body.parentElement;
8
+
9
+ if (!html) {
10
+ return noop;
11
+ }
12
+
13
+ const old = html.dir;
14
+ html.dir = direction;
15
+
16
+ return () => {
17
+ html.dir = old;
18
+ };
19
+ }, [direction]);
20
+ };
21
+
22
+ export default useDirection;
@@ -0,0 +1,22 @@
1
+ import { useLayoutEffect } from 'react';
2
+
3
+ import { noop } from 'lib';
4
+
5
+ const useLanguage = (language: string) => {
6
+ useLayoutEffect(() => {
7
+ const html = document.body.parentElement;
8
+
9
+ if (!html) {
10
+ return noop;
11
+ }
12
+
13
+ const old = html.lang;
14
+ html.lang = language;
15
+
16
+ return () => {
17
+ html.lang = old;
18
+ };
19
+ }, [language]);
20
+ };
21
+
22
+ export default useLanguage;