@scrabble-solver/scrabble-solver 2.11.2 → 2.11.4

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 (88) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  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/210.js +109 -0
  16. package/.next/server/chunks/277.js +189 -125
  17. package/.next/server/chunks/44.js +19 -0
  18. package/.next/server/chunks/987.js +91 -0
  19. package/.next/server/middleware-build-manifest.js +1 -1
  20. package/.next/server/pages/404.html +2 -2
  21. package/.next/server/pages/404.js.nft.json +1 -1
  22. package/.next/server/pages/500.html +1 -1
  23. package/.next/server/pages/_app.js +1 -73
  24. package/.next/server/pages/_app.js.nft.json +1 -1
  25. package/.next/server/pages/_document.js.nft.json +1 -1
  26. package/.next/server/pages/_error.js.nft.json +1 -1
  27. package/.next/server/pages/api/dictionary/[locale]/[word].js +3 -3
  28. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  29. package/.next/server/pages/api/dictionary/[locale].js +3 -3
  30. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  31. package/.next/server/pages/api/solve.js +10 -6
  32. package/.next/server/pages/api/solve.js.nft.json +1 -1
  33. package/.next/server/pages/api/verify.js +5 -4
  34. package/.next/server/pages/api/verify.js.nft.json +1 -1
  35. package/.next/server/pages/api/visit.js +3 -3
  36. package/.next/server/pages/api/visit.js.nft.json +1 -1
  37. package/.next/server/pages/index.html +1 -1
  38. package/.next/server/pages/index.js +104 -207
  39. package/.next/server/pages/index.js.nft.json +1 -1
  40. package/.next/server/pages/index.json +1 -1
  41. package/.next/static/{Mdvi3FY0PqkILKLbPlVBU → MvHZRF4XuJ7g8LLLRkf8U}/_buildManifest.js +1 -1
  42. package/.next/static/chunks/pages/_app-66d80a5594aab8dc.js +28 -0
  43. package/.next/static/chunks/pages/index-0858deea02b2a417.js +1 -0
  44. package/.next/static/css/885da289cec275b3.css +1 -0
  45. package/.next/static/css/ea1c8134fe9a143e.css +2 -0
  46. package/.next/trace +53 -54
  47. package/package.json +9 -9
  48. package/src/api/index.ts +1 -0
  49. package/src/api/isCellValid.ts +3 -2
  50. package/src/api/isCharacterValid.ts +13 -0
  51. package/src/components/Button/Button.module.scss +14 -1
  52. package/src/components/Modal/Modal.module.scss +11 -3
  53. package/src/components/NotFound/NotFound.module.scss +13 -4
  54. package/src/components/NotFound/NotFound.tsx +4 -7
  55. package/src/components/Rack/Rack.tsx +3 -1
  56. package/src/components/Tile/Tile.module.scss +23 -18
  57. package/src/components/Tile/Tile.tsx +2 -5
  58. package/src/components/Tile/TilePure.tsx +1 -2
  59. package/src/i18n/constants.ts +65 -0
  60. package/src/i18n/de.json +3 -2
  61. package/src/i18n/en.json +3 -2
  62. package/src/i18n/es.json +3 -2
  63. package/src/i18n/fa.json +2 -1
  64. package/src/i18n/fr.json +3 -2
  65. package/src/i18n/i18n.module.scss +27 -0
  66. package/src/i18n/pl.json +3 -2
  67. package/src/icons/DashCircleFill.svg +1 -0
  68. package/src/icons/EyeFill.svg +5 -0
  69. package/src/icons/index.ts +3 -2
  70. package/src/modals/MenuModal/MenuModal.module.scss +17 -1
  71. package/src/modals/MenuModal/MenuModal.tsx +8 -2
  72. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -1
  73. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +8 -0
  74. package/src/modals/ResultsModal/ResultsModal.tsx +35 -11
  75. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +3 -44
  76. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +4 -2
  77. package/src/pages/api/solve.ts +3 -2
  78. package/src/styles/mixins.scss +2 -0
  79. package/src/styles/variables.scss +12 -0
  80. package/src/types/index.ts +1 -0
  81. package/.next/server/chunks/417.js +0 -221
  82. package/.next/server/chunks/664.js +0 -621
  83. package/.next/static/chunks/pages/_app-495e6f4ccc278bb2.js +0 -28
  84. package/.next/static/chunks/pages/index-5ecc51900ca29685.js +0 -1
  85. package/.next/static/css/17b0a2db8742105f.css +0 -1
  86. package/.next/static/css/e1ffeb2558330c55.css +0 -2
  87. package/src/modals/SettingsModal/components/LocaleSetting/options.ts +0 -68
  88. /package/.next/static/{Mdvi3FY0PqkILKLbPlVBU → MvHZRF4XuJ7g8LLLRkf8U}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.11.2",
3
+ "version": "2.11.4",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,13 +31,13 @@
31
31
  "@floating-ui/react": "^0.20.1",
32
32
  "@kamilmielnik/trie": "^2.0.1",
33
33
  "@reduxjs/toolkit": "^1.9.3",
34
- "@scrabble-solver/configs": "^2.11.2",
35
- "@scrabble-solver/constants": "^2.11.2",
36
- "@scrabble-solver/dictionaries": "^2.11.2",
37
- "@scrabble-solver/logger": "^2.11.2",
38
- "@scrabble-solver/solver": "^2.11.2",
39
- "@scrabble-solver/types": "^2.11.2",
40
- "@scrabble-solver/word-definitions": "^2.11.2",
34
+ "@scrabble-solver/configs": "^2.11.4",
35
+ "@scrabble-solver/constants": "^2.11.4",
36
+ "@scrabble-solver/dictionaries": "^2.11.4",
37
+ "@scrabble-solver/logger": "^2.11.4",
38
+ "@scrabble-solver/solver": "^2.11.4",
39
+ "@scrabble-solver/types": "^2.11.4",
40
+ "@scrabble-solver/word-definitions": "^2.11.4",
41
41
  "classnames": "^2.3.2",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
@@ -72,5 +72,5 @@
72
72
  "sass": "^1.59.2",
73
73
  "workbox-webpack-plugin": "^6.5.4"
74
74
  },
75
- "gitHead": "b7ac80ab2a6c0af218756953e354d1e0c7f1e58a"
75
+ "gitHead": "41fd187eab48d417aafc30f8a1d407178323c415"
76
76
  }
package/src/api/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as getServerLoggingData } from './getServerLoggingData';
2
2
  export { default as isBoardValid } from './isBoardValid';
3
3
  export { default as isCellValid } from './isCellValid';
4
+ export { default as isCharacterValid } from './isCharacterValid';
4
5
  export { default as isRowValid } from './isRowValid';
@@ -1,6 +1,7 @@
1
- import { BLANK } from '@scrabble-solver/constants';
2
1
  import { CellJson, Config } from '@scrabble-solver/types';
3
2
 
3
+ import isCharacterValid from './isCharacterValid';
4
+
4
5
  const isCellValid = (cell: CellJson, config: Config): boolean => {
5
6
  const { isEmpty, tile, x, y } = cell;
6
7
 
@@ -16,7 +17,7 @@ const isCellValid = (cell: CellJson, config: Config): boolean => {
16
17
  return false;
17
18
  }
18
19
 
19
- if (tile !== null && !config.hasCharacter(tile.character) && tile.character !== BLANK) {
20
+ if (tile !== null && !isCharacterValid(tile.character)) {
20
21
  return false;
21
22
  }
22
23
 
@@ -0,0 +1,13 @@
1
+ const TWO_TILE_CHARACTER_LENGTH = 2;
2
+ const MAX_CHARACTER_LENGTH = TWO_TILE_CHARACTER_LENGTH;
3
+
4
+ const isCharacterValid = (character: string): boolean => {
5
+ /*
6
+ * We could be strict here and check whether config.hasCharacter(character) || character === BLANK
7
+ * but since this case won't really affect/break solving, we don't need to worry about it.
8
+ * It's better to display an empty state than error state in UI, so this is a sanity check only.
9
+ */
10
+ return character.length !== 0 && character.length <= MAX_CHARACTER_LENGTH;
11
+ };
12
+
13
+ export default isCharacterValid;
@@ -60,16 +60,29 @@
60
60
  .content {
61
61
  display: flex;
62
62
  align-items: center;
63
- gap: 20px;
63
+ gap: var(--spacing--l);
64
+
64
65
  }
65
66
 
66
67
  .icon {
68
+ flex: 0 0 auto;
67
69
  width: var(--button--icon--size);
68
70
  height: var(--button--icon--size);
69
71
  transition: var(--transition);
70
72
  }
71
73
 
72
74
  .label {
75
+ @include ellipsis;
76
+
77
+ flex: 1;
73
78
  font-size: var(--font--size--h3);
74
79
  transition: var(--transition);
80
+
81
+ [dir='ltr'] & {
82
+ text-align: left;
83
+ }
84
+
85
+ [dir='rtl'] & {
86
+ text-align: right;
87
+ }
75
88
  }
@@ -89,6 +89,7 @@
89
89
  z-index: 1;
90
90
  flex: 0 0 auto;
91
91
  padding: var(--spacing--l);
92
+ padding-bottom: 0;
92
93
  background-color: var(--color--background);
93
94
  }
94
95
 
@@ -117,18 +118,25 @@
117
118
  position: relative;
118
119
  flex: 1;
119
120
  min-height: 0;
120
- padding: var(--spacing--l);
121
121
  overflow: auto;
122
+ padding: var(--spacing--l);
122
123
  }
123
124
 
124
125
  .footer {
125
126
  flex: 0 0 auto;
126
127
  display: flex;
127
- justify-content: flex-end;
128
+ flex-direction: row-reverse;
129
+ justify-content: space-between;
130
+ gap: var(--spacing--l);
128
131
  padding: var(--spacing--l);
129
- box-shadow: var(--box-shadow);
132
+ padding-top: 0;
130
133
 
131
134
  @include media('>s') {
132
135
  display: none;
133
136
  }
137
+
138
+ > * {
139
+ min-width: 0;
140
+ flex: 1;
141
+ }
134
142
  }
@@ -1,3 +1,5 @@
1
+ @import 'styles/mixins';
2
+
1
3
  .notFound {
2
4
  display: flex;
3
5
  align-items: center;
@@ -11,18 +13,25 @@
11
13
  align-items: center;
12
14
  justify-content: center;
13
15
  flex-direction: column;
14
- padding: var(--spacing--xxl);
16
+ padding: var(--spacing--l);
15
17
  }
16
18
 
17
19
  .icon {
18
- $size: 204px;
20
+ --size: 200px;
19
21
 
20
- height: $size;
21
- width: $size;
22
+ height: var(--size);
23
+ width: var(--size);
22
24
  margin-bottom: var(--spacing--xxl);
23
25
  color: var(--color--red);
26
+
27
+ @include media('<xs') {
28
+ --size: 50%;
29
+
30
+ margin-bottom: var(--spacing--xl);
31
+ }
24
32
  }
25
33
 
26
34
  .tiles {
35
+ max-width: 100%;
27
36
  height: 60px;
28
37
  }
@@ -1,4 +1,3 @@
1
- import Link from 'next/link';
2
1
  import { FunctionComponent } from 'react';
3
2
 
4
3
  import { DashCircleFill } from 'icons';
@@ -11,12 +10,10 @@ const CONTENT = [['HTTP', '404']];
11
10
 
12
11
  const NotFound: FunctionComponent = () => (
13
12
  <div className={styles.notFound}>
14
- <Link href="/">
15
- <a className={styles.link}>
16
- <DashCircleFill className={styles.icon} />
17
- <PlainTiles className={styles.tiles} content={CONTENT} />
18
- </a>
19
- </Link>
13
+ <a className={styles.link} href="/">
14
+ <DashCircleFill className={styles.icon} />
15
+ <PlainTiles className={styles.tiles} content={CONTENT} />
16
+ </a>
20
17
  </div>
21
18
  );
22
19
 
@@ -8,6 +8,7 @@ import {
8
8
  createKeyboardNavigation,
9
9
  extractCharacters,
10
10
  extractInputValue,
11
+ getTileSizes,
11
12
  zipCharactersAndTiles,
12
13
  } from 'lib';
13
14
  import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
@@ -31,6 +32,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
31
32
  const tilesRefs = useMemo(() => createArray(tilesCount).map(() => createRef<HTMLInputElement>()), [tilesCount]);
32
33
  const activeIndexRef = useRef<number>();
33
34
  const { direction } = LOCALE_FEATURES[locale];
35
+ const { tileFontSize } = getTileSizes(tileSize);
34
36
 
35
37
  const changeActiveIndex = useCallback(
36
38
  (offset: number) => {
@@ -110,7 +112,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
110
112
  }, [changeActiveIndex, config, direction]);
111
113
 
112
114
  return (
113
- <div className={classNames(styles.rack, className)} onPaste={handlePaste}>
115
+ <div className={classNames(styles.rack, className)} style={{ fontSize: tileFontSize }} onPaste={handlePaste}>
114
116
  {tiles.map(({ character, tile }, index) => (
115
117
  <RackTile
116
118
  activeIndexRef={activeIndexRef}
@@ -34,18 +34,10 @@
34
34
  }
35
35
 
36
36
  &.raised {
37
- --shadow--size: 2px;
38
- --shadow--blur: 2px;
39
- --shadow--spread: -1px;
40
- --shadow--color: rgba(34, 34, 34, 0.8);
41
-
42
- box-shadow: inset calc(-1 * var(--shadow--size)) calc(-1 * var(--shadow--size)) var(--shadow--blur)
43
- var(--shadow--spread) var(--shadow--color);
37
+ box-shadow: var(--box-shadow--raised);
44
38
 
45
39
  @include media('<xs') {
46
- --shadow--size: 1px;
47
- --shadow--spread: 0;
48
- --shadow--blur: 1px;
40
+ box-shadow: var(--box-shadow--raised--subtle);
49
41
  }
50
42
  }
51
43
 
@@ -59,6 +51,13 @@
59
51
  color: var(--color--inactive);
60
52
  }
61
53
 
54
+ &.invalid {
55
+ --background-color: var(--color--foreground);
56
+ --shadow--color: var(--box-shadow--color--inverse);
57
+
58
+ color: var(--color--white);
59
+ }
60
+
62
61
  &.highlighted {
63
62
  --background-color: var(--color--primary);
64
63
 
@@ -114,23 +113,29 @@
114
113
  }
115
114
 
116
115
  .alert {
117
- $size: 30%;
116
+ --size: 30%;
118
117
 
119
118
  position: absolute;
120
- width: $size;
121
- height: $size;
122
- background-color: var(--color--error--opposite);
119
+ width: var(--size);
120
+ height: var(--size);
121
+ background: radial-gradient(
122
+ var(--color--error--opposite),
123
+ var(--color--error--opposite) 85%,
124
+ transparent 85%,
125
+ transparent
126
+ );
123
127
  color: var(--color--error);
128
+ pointer-events: none;
124
129
 
125
130
  [dir='ltr'] & {
126
131
  top: 0;
127
132
  right: 0;
128
- border-top-right-radius: inherit;
133
+ border-bottom-right-radius: inherit;
129
134
  }
130
135
 
131
136
  [dir='rtl'] & {
132
- bottom: 0;
133
- right: 0;
134
- border-bottom-right-radius: inherit;
137
+ top: 0;
138
+ left: 0;
139
+ border-top-left-radius: inherit;
135
140
  }
136
141
  }
@@ -59,11 +59,8 @@ const Tile: FunctionComponent<Props> = ({
59
59
  }) => {
60
60
  const locale = useTypedSelector(selectLocale);
61
61
  const { animateTile, showTilePoints } = useAppLayout();
62
- const { pointsFontSize, tileFontSize, tileSize } = getTileSizes(size);
63
- const style = useMemo(
64
- () => ({ fontSize: tileFontSize, height: tileSize, width: tileSize }),
65
- [tileSize, tileFontSize],
66
- );
62
+ const { pointsFontSize, tileSize } = getTileSizes(size);
63
+ const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
67
64
  const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
68
65
  const ref = useRef<HTMLInputElement>(null);
69
66
  const mergedRef = useMergeRefs(inputRef ? [ref, inputRef] : [ref]);
@@ -61,10 +61,9 @@ const TilePure: FunctionComponent<Props> = ({
61
61
  <div
62
62
  className={classNames(styles.tile, className, {
63
63
  [styles.blank]: isBlank,
64
- [styles.disabled]: disabled,
65
64
  [styles.empty]: !character,
65
+ [styles.invalid]: !isValid,
66
66
  [styles.highlighted]: highlighted,
67
- [styles.placeholder]: !character,
68
67
  [styles.points1]: points === 1,
69
68
  [styles.points2]: points === 2,
70
69
  [styles.points3]: points === 3,
@@ -1,4 +1,9 @@
1
1
  import { Locale } from '@scrabble-solver/types';
2
+ import { FunctionComponent, SVGAttributes } from 'react';
3
+
4
+ import { FlagDe, FlagEs, FlagFa, FlagFr, FlagGb, FlagPl, FlagUs } from 'icons';
5
+
6
+ import styles from './i18n.module.scss';
2
7
 
3
8
  interface LocaleFeatures {
4
9
  direction: 'ltr' | 'rtl';
@@ -51,3 +56,63 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
51
56
  vowels: true,
52
57
  },
53
58
  };
59
+
60
+ interface Flag {
61
+ className: string;
62
+ Icon: FunctionComponent<SVGAttributes<SVGElement>>;
63
+ label: string;
64
+ name: string;
65
+ value: Locale;
66
+ }
67
+
68
+ export const LOCALE_FLAGS: Record<Locale, Flag> = {
69
+ [Locale.EN_GB]: {
70
+ className: styles.gb,
71
+ Icon: FlagGb,
72
+ label: 'English (GB)',
73
+ name: 'English (GB)',
74
+ value: Locale.EN_GB,
75
+ },
76
+ [Locale.EN_US]: {
77
+ className: styles.us,
78
+ Icon: FlagUs,
79
+ label: 'English (US)',
80
+ name: 'English (US)',
81
+ value: Locale.EN_US,
82
+ },
83
+ [Locale.FA_IR]: {
84
+ className: styles.fa,
85
+ Icon: FlagFa,
86
+ label: 'فارسی',
87
+ name: 'Persian',
88
+ value: Locale.FA_IR,
89
+ },
90
+ [Locale.FR_FR]: {
91
+ className: styles.fr,
92
+ Icon: FlagFr,
93
+ label: 'Français',
94
+ name: 'French',
95
+ value: Locale.FR_FR,
96
+ },
97
+ [Locale.DE_DE]: {
98
+ className: styles.de,
99
+ Icon: FlagDe,
100
+ label: 'Deutsch',
101
+ name: 'German',
102
+ value: Locale.DE_DE,
103
+ },
104
+ [Locale.PL_PL]: {
105
+ className: styles.pl,
106
+ Icon: FlagPl,
107
+ label: 'Polski',
108
+ name: 'Polish',
109
+ value: Locale.PL_PL,
110
+ },
111
+ [Locale.ES_ES]: {
112
+ className: styles.es,
113
+ Icon: FlagEs,
114
+ label: 'Español',
115
+ name: 'Spanish',
116
+ value: Locale.ES_ES,
117
+ },
118
+ };
package/src/i18n/de.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "Wort existiert im Wörterbuch aber hat keine Definition.",
22
22
  "dictionary.empty-state.no-results": "Wort kann nicht im Wörterbuch gefunden werden.",
23
23
  "dictionary.empty-state.not-allowed": "Dieses Wort ist nicht erlaubt.",
24
- "dictionary.empty-state.uninitialized": "Die Wörterbuchdefinition des letzten markierten Wortes wird hier angezeigt.",
24
+ "dictionary.empty-state.uninitialized": "Die Wörterbuchdéfinition wird hier angezeigt.",
25
25
  "dictionary.input.placeholder": "Durchsuche Wörterbuch...",
26
26
  "dictionary.input.title": "Durch Kommas getrennte Wörter",
27
27
  "empty-state.error": "Fehler",
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "Restliche Steine",
47
47
  "results": "Ergebnisse",
48
48
  "results.empty-state.no-results": "Keine Ergebnisse - kein Wort konnte generiert werden.",
49
- "results.empty-state.outdated": "Ergebnisse sind alt. Klicken zum Aktualisieren.",
49
+ "results.empty-state.outdated": "Ergebnisse sind alt.",
50
50
  "results.empty-state.uninitialized": "Wörter die aus deinen Buchstaben generiert wurden erscheinen hier.",
51
51
  "results.input.placeholder": "Suchergebnisse... (RegExp)",
52
52
  "results.insert": "Hinzufügen",
53
+ "results.preview": "Vorschau",
53
54
  "results.solve": "Lösen",
54
55
  "settings": "Einstellungen",
55
56
  "settings.autoGroupTiles": "Restliche Steine gruppieren",
package/src/i18n/en.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "Word exists in the dictionary but it does not have a definition.",
22
22
  "dictionary.empty-state.no-results": "Unable to find word definition in the dictionary.",
23
23
  "dictionary.empty-state.not-allowed": "This word is not allowed.",
24
- "dictionary.empty-state.uninitialized": "Dictionary definition of the most recently highlighted word will be shown here.",
24
+ "dictionary.empty-state.uninitialized": "Word definition will be shown here.",
25
25
  "dictionary.input.placeholder": "Search dictionary...",
26
26
  "dictionary.input.title": "Comma-separated words",
27
27
  "empty-state.error": "Error",
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "Remaining tiles",
47
47
  "results": "Results",
48
48
  "results.empty-state.no-results": "No results - unable to generate any words.",
49
- "results.empty-state.outdated": "Results are outdated. Click below to update.",
49
+ "results.empty-state.outdated": "Results are outdated.",
50
50
  "results.empty-state.uninitialized": "Words generated from your letters will be shown here.",
51
51
  "results.input.placeholder": "Search results... (RegExp)",
52
52
  "results.insert": "Insert",
53
+ "results.preview": "Preview",
53
54
  "results.solve": "Solve",
54
55
  "settings": "Settings",
55
56
  "settings.autoGroupTiles": "Group remaining tiles",
package/src/i18n/es.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "La palabra existe en el diccionario pero no tiene una definición.",
22
22
  "dictionary.empty-state.no-results": "No se puede encontrar la definición de palabra en el diccionario.",
23
23
  "dictionary.empty-state.not-allowed": "Esta palabra no es aceptable.",
24
- "dictionary.empty-state.uninitialized": "Aquí se mostrará la definición del diccionario de la última palabra resaltada.",
24
+ "dictionary.empty-state.uninitialized": "Aquí se mostrará la definición del diccionario.",
25
25
  "dictionary.input.placeholder": "Busca el diccionario...",
26
26
  "dictionary.input.title": "Palabras separadas por comas",
27
27
  "empty-state.error": "Error",
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "Casillas restantes",
47
47
  "results": "Resultados",
48
48
  "results.empty-state.no-results": "No hay resultados; no se pueden generar palabras",
49
- "results.empty-state.outdated": "Los resultados están desactualizados. Haga clic a continuación para actualizar.",
49
+ "results.empty-state.outdated": "Los resultados están desactualizados.",
50
50
  "results.empty-state.uninitialized": "Aquí se mostrarán las palabras generadas a partir de sus letras.",
51
51
  "results.input.placeholder": "Busque una solución... (RegExp)",
52
52
  "results.insert": "Insertar",
53
+ "results.preview": "Vista previa",
53
54
  "results.solve": "Resolver",
54
55
  "settings": "Configuración",
55
56
  "settings.autoGroupTiles": "Agrupar casillas restantes",
package/src/i18n/fa.json CHANGED
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "کاشی های باقی مانده",
47
47
  "results": "نتایج",
48
48
  "results.empty-state.no-results": "کلمه قابل استفاده پیدا نشد.",
49
- "results.empty-state.outdated": "نتایج به روز نیستند، برای بروز رسانی کلیک کنید.",
49
+ "results.empty-state.outdated": "نتایج به روز نیستند، برای بروز.",
50
50
  "results.empty-state.uninitialized": "کلمات تولید شده از حروف شما اینجا نمایش داده خواهد شد.",
51
51
  "results.input.placeholder": "جستجو در نتایج (RegExp)",
52
52
  "results.insert": "وارد کردن",
53
+ "results.preview": "پیش نمایش",
53
54
  "results.solve": "حل کن",
54
55
  "settings": "تنظیمات",
55
56
  "settings.autoGroupTiles": "کاشی های باقی مانده ی طاقچه را کنار هم قرار بده",
package/src/i18n/fr.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "Le mot existe dans le dictionary mais n'a pas de définition.",
22
22
  "dictionary.empty-state.no-results": "Impossible de trouver une définition pour ce mot dans le dictionaire.",
23
23
  "dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
24
- "dictionary.empty-state.uninitialized": "La définition dictionaire du dernier mot surligné sera affichée ici.",
24
+ "dictionary.empty-state.uninitialized": "La définition dictionaire sera affichée ici.",
25
25
  "dictionary.input.placeholder": "Rechercher dans le dictionnaire...",
26
26
  "dictionary.input.title": "Mots séparées par des virgules",
27
27
  "empty-state.error": "Erreur",
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "Cases restantes",
47
47
  "results": "Résultats",
48
48
  "results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
49
- "results.empty-state.outdated": "Les résultats sont dépassé. Cliquer ci-dessous pour mettre à jour.",
49
+ "results.empty-state.outdated": "Les résultats sont dépassé.",
50
50
  "results.empty-state.uninitialized": "Les mots générés à partir de vos lettres seront affichés ici.",
51
51
  "results.input.placeholder": "Rechercher les résultats... (RegExp)",
52
52
  "results.insert": "Inserer",
53
+ "results.preview": "Prévisualisation",
53
54
  "results.solve": "Résoudre",
54
55
  "settings": "Options",
55
56
  "settings.autoGroupTiles": "Grouper les cases restantes",
@@ -0,0 +1,27 @@
1
+ .de {
2
+ --aspect-ratio: var(--flag--de--aspect-ratio);
3
+ }
4
+
5
+ .es {
6
+ --aspect-ratio: var(--flag--es--aspect-ratio);
7
+ }
8
+
9
+ .fa {
10
+ --aspect-ratio: var(--flag--fa--aspect-ratio);
11
+ }
12
+
13
+ .fr {
14
+ --aspect-ratio: var(--flag--fr--aspect-ratio);
15
+ }
16
+
17
+ .gb {
18
+ --aspect-ratio: var(--flag--gb--aspect-ratio);
19
+ }
20
+
21
+ .pl {
22
+ --aspect-ratio: var(--flag--pl--aspect-ratio);
23
+ }
24
+
25
+ .us {
26
+ --aspect-ratio: var(--flag--us--aspect-ratio);
27
+ }
package/src/i18n/pl.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "Słowo istnieje w słowniku ale nie posiada definicji.",
22
22
  "dictionary.empty-state.no-results": "Nie udało się znaleźć definicji słowa w słowniku.",
23
23
  "dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
24
- "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona słownikowa definicja ostatnio podświetlonego słowa.",
24
+ "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona definicja słowa.",
25
25
  "dictionary.input.placeholder": "Szukaj w słowniku...",
26
26
  "dictionary.input.title": "Słowa rozdzielone przecinkiem",
27
27
  "empty-state.error": "Błąd",
@@ -46,10 +46,11 @@
46
46
  "remaining-tiles": "Pozostałe płytki",
47
47
  "results": "Wyniki",
48
48
  "results.empty-state.no-results": "Brak wyników - nie można wygenerować żadnego słowa.",
49
- "results.empty-state.outdated": "Wyniki są nieaktualne. Kliknij poniżej, aby zaktualizować.",
49
+ "results.empty-state.outdated": "Wyniki są nieaktualne.",
50
50
  "results.empty-state.uninitialized": "Tu zostaną wyświetlone słowa wygenerowane z Twoich liter.",
51
51
  "results.input.placeholder": "Szukaj rozwiązania... (RegExp)",
52
52
  "results.insert": "Wstaw",
53
+ "results.preview": "Podgląd",
53
54
  "results.solve": "Rozwiąż",
54
55
  "settings": "Opcje",
55
56
  "settings.autoGroupTiles": "Grupuj pozostałe płytki",
@@ -1,3 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/dash-circle-fill/ -->
1
2
  <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
2
3
  <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM4.5 7.5a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7z" fill="currentColor" />
3
4
  </svg>
@@ -0,0 +1,5 @@
1
+ <!-- https://icons.getbootstrap.com/icons/eye-fill/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z" fill="currentColor" />
4
+ <path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z" fill="currentColor" />
5
+ </svg>
@@ -18,15 +18,16 @@ export { default as DashCircleFill } from './DashCircleFill.svg';
18
18
  export { default as Eraser } from './Eraser.svg';
19
19
  export { default as ExclamationSquareFill } from './ExclamationSquareFill.svg';
20
20
  export { default as ExclamationTriangleFill } from './ExclamationTriangleFill.svg';
21
+ export { default as EyeFill } from './EyeFill.svg';
21
22
  export { default as Flag } from './Flag.svg';
22
- export { default as FlagFill } from './FlagFill.svg';
23
+ export { default as FlagDe } from './FlagDe.svg';
23
24
  export { default as FlagEs } from './FlagEs.svg';
24
25
  export { default as FlagFa } from './FlagFa.svg';
26
+ export { default as FlagFill } from './FlagFill.svg';
25
27
  export { default as FlagFr } from './FlagFr.svg';
26
28
  export { default as FlagGb } from './FlagGb.svg';
27
29
  export { default as FlagPl } from './FlagPl.svg';
28
30
  export { default as FlagUs } from './FlagUs.svg';
29
- export { default as FlagDe } from './FlagDe.svg';
30
31
  export { default as Github } from './Github.svg';
31
32
  export { default as InfoCircleFill } from './InfoCircleFill.svg';
32
33
  export { default as Keyboard } from './Keyboard.svg';
@@ -8,10 +8,26 @@
8
8
  &:active,
9
9
  &:focus,
10
10
  &:hover {
11
- box-shadow: 0px 1px 1px 0px var(--box-shadow--color);
11
+ box-shadow: 0 1px 1px 0 var(--box-shadow--color);
12
12
  }
13
13
 
14
14
  & + & {
15
15
  margin-top: var(--spacing--l);
16
16
  }
17
17
  }
18
+
19
+ .settings {
20
+ width: 100%;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: space-between;
24
+ }
25
+
26
+ .flag {
27
+ --height: var(--button--icon--size);
28
+
29
+ width: calc(var(--height) * var(--aspect--ratio));
30
+ height: var(--height);
31
+ border-radius: var(--border--radius);
32
+ box-shadow: 0 0 0 1px var(--box-shadow--color);
33
+ }
@@ -1,9 +1,10 @@
1
1
  import { FunctionComponent } from 'react';
2
2
 
3
3
  import { Button, Modal } from 'components';
4
+ import { LOCALE_FLAGS } from 'i18n';
4
5
  import { BookHalf, CardChecklist, Cog, Github, Sack } from 'icons';
5
6
  import { GITHUB_PROJECT_URL } from 'parameters';
6
- import { useTranslate } from 'state';
7
+ import { selectLocale, useTranslate, useTypedSelector } from 'state';
7
8
 
8
9
  import styles from './MenuModal.module.scss';
9
10
 
@@ -27,6 +28,8 @@ const Menu: FunctionComponent<Props> = ({
27
28
  onShowWords,
28
29
  }) => {
29
30
  const translate = useTranslate();
31
+ const locale = useTypedSelector(selectLocale);
32
+ const Flag = LOCALE_FLAGS[locale];
30
33
 
31
34
  return (
32
35
  <Modal className={className} isOpen={isOpen} title={translate('menu')} onClose={onClose}>
@@ -59,7 +62,10 @@ const Menu: FunctionComponent<Props> = ({
59
62
  </Button.Link>
60
63
 
61
64
  <Button aria-label={translate('settings')} className={styles.button} Icon={Cog} onClick={onShowSettings}>
62
- {translate('settings')}
65
+ <div className={styles.settings}>
66
+ <span>{translate('settings')}</span>
67
+ <Flag.Icon className={styles.flag} />
68
+ </div>
63
69
  </Button>
64
70
  </Modal>
65
71
  );