Compare commits
3 Commits
codex/inte
...
codex/inte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e49e9601 | ||
|
|
a87eff1578 | ||
|
|
5795fd4a1f |
170
CHANGELOG.md
170
CHANGELOG.md
@@ -1,6 +1,176 @@
|
||||
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](https://github.com/antialias/soroban-abacus-flashcards/commit/79f7347d4800646378470a7f9aca8e7f2fd5573c))
|
||||
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](https://github.com/antialias/soroban-abacus-flashcards/commit/3628426a567d7e0273be75cce64632ae04b7d5eb))
|
||||
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](https://github.com/antialias/soroban-abacus-flashcards/commit/1d525c7b5320984a1582b8ab7eae57895c728428))
|
||||
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](https://github.com/antialias/soroban-abacus-flashcards/commit/2fc0a05f7f557cee55f7d31b585499dd04e68ff9))
|
||||
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](https://github.com/antialias/soroban-abacus-flashcards/commit/d568955d6abf389e6ab7c6979e33122a65917a46))
|
||||
* **arcade:** auto-create room when user has none ([ff88c3a](https://github.com/antialias/soroban-abacus-flashcards/commit/ff88c3a1b81703a87a1d57eeb5cc139da7d9df04))
|
||||
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](https://github.com/antialias/soroban-abacus-flashcards/commit/1461414ef4d0b213af241213447c91eed1abe5fb))
|
||||
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](https://github.com/antialias/soroban-abacus-flashcards/commit/780a7161bc05c2ca6597d7d8d89f01afd33d9f4d))
|
||||
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](https://github.com/antialias/soroban-abacus-flashcards/commit/4d8e873358271fe3fd50b228aea8277e20aa5966))
|
||||
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](https://github.com/antialias/soroban-abacus-flashcards/commit/656f5a7838ed6003c214ec484d4c37072270fa8d))
|
||||
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](https://github.com/antialias/soroban-abacus-flashcards/commit/6527c26a8166b23f074e85eb335a15800c1947a2))
|
||||
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](https://github.com/antialias/soroban-abacus-flashcards/commit/d25b888ffb3915d2d482442ab708ba3e159af512))
|
||||
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](https://github.com/antialias/soroban-abacus-flashcards/commit/fd765335efbc91366c596c7789b92882cd3379d9))
|
||||
* **card-sorting:** add green border to correctly positioned cards ([16fca86](https://github.com/antialias/soroban-abacus-flashcards/commit/16fca86b7687115f1cf565c533a512e92946e3a8)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
|
||||
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](https://github.com/antialias/soroban-abacus-flashcards/commit/3a8209975728cdcf914c43ba08339454a9e2457f))
|
||||
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](https://github.com/antialias/soroban-abacus-flashcards/commit/c367e0ceece41d8e7c2bc8aebe3239ff6053a115))
|
||||
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](https://github.com/antialias/soroban-abacus-flashcards/commit/b0b93d0175c8a1c8958d6ba346d969c234fdd6ff))
|
||||
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](https://github.com/antialias/soroban-abacus-flashcards/commit/ee7345d641e0ee72915afb9cdbd6d284b7e238bd)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
|
||||
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](https://github.com/antialias/soroban-abacus-flashcards/commit/ed6f1779141d0bc9dff2d532a3dfc638015936b5)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
|
||||
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](https://github.com/antialias/soroban-abacus-flashcards/commit/f6ed4a27a26d8bfa495ba5f580a446286b9674a0))
|
||||
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](https://github.com/antialias/soroban-abacus-flashcards/commit/4ba7f247175d93e4d339e2be7bbdb2e009992232))
|
||||
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](https://github.com/antialias/soroban-abacus-flashcards/commit/7028cfc51164e9219479e6040b03c29239aa7edb))
|
||||
* **card-sorting:** gentler spring animation for locked cards ([47189cb](https://github.com/antialias/soroban-abacus-flashcards/commit/47189cb6e79ed2915f5ddcc9cb3626540dfb07f3))
|
||||
* **card-sorting:** implement continuous bezier curve paths ([2d93024](https://github.com/antialias/soroban-abacus-flashcards/commit/2d9302410f5e98145a435b00df3ae5fcf3f4c0b5))
|
||||
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](https://github.com/antialias/soroban-abacus-flashcards/commit/0b0503f0354a4a82fe6b9bfe827729e8e5a9e329))
|
||||
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](https://github.com/antialias/soroban-abacus-flashcards/commit/2e7a02c9e4ab84e821d58661d6e7a326f7882afb))
|
||||
* **card-sorting:** optimize results screen for mobile ([d188789](https://github.com/antialias/soroban-abacus-flashcards/commit/d188789069b4c350ce3cc0d221bd4a43dab528e0))
|
||||
* **card-sorting:** redesign setup screen with modern UI ([73cf967](https://github.com/antialias/soroban-abacus-flashcards/commit/73cf96749234c480482f62392245b38c1fd5f0a0))
|
||||
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](https://github.com/antialias/soroban-abacus-flashcards/commit/222dc555fa5068e2594dcc074e33f70320f5742c))
|
||||
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](https://github.com/antialias/soroban-abacus-flashcards/commit/8f6feec4f21d0af0d1c98daf5017eddd91d3d578))
|
||||
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](https://github.com/antialias/soroban-abacus-flashcards/commit/c5f39d51eb45ec816f32151dc7f9d7c06360474b))
|
||||
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](https://github.com/antialias/soroban-abacus-flashcards/commit/e3184dd0d444e5dc204731f5b396d5c553cf7d11))
|
||||
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](https://github.com/antialias/soroban-abacus-flashcards/commit/83d0ba26f5eeec3e189d279710d5bbcf13e82f29))
|
||||
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](https://github.com/antialias/soroban-abacus-flashcards/commit/fe9bfeabf9ee66923501b18e1b69f2d666d0817d))
|
||||
* **i18n:** add global language selector to navigation ([0506360](https://github.com/antialias/soroban-abacus-flashcards/commit/0506360117807665e8f5a6fcd8f1178339f6e65c))
|
||||
* **i18n:** add homepage translations for all supported languages ([8c9d35a](https://github.com/antialias/soroban-abacus-flashcards/commit/8c9d35a3b43dd29664f5afb1bd96c4e584d9ec75))
|
||||
* **i18n:** internationalize homepage with English translations ([40cff14](https://github.com/antialias/soroban-abacus-flashcards/commit/40cff143c72e9228d7cce607cab64c4a6d067017))
|
||||
* **i18n:** migrate from react-i18next to next-intl ([9016b76](https://github.com/antialias/soroban-abacus-flashcards/commit/9016b760247a20271255839e4dd7e5b9a8353b9f))
|
||||
* internationalize tutorial player ([26d41cf](https://github.com/antialias/soroban-abacus-flashcards/commit/26d41cfd058bfdf5b61ee6e20cfc61cbecb32f45))
|
||||
* optimize card sorting for mobile displays ([b443ee9](https://github.com/antialias/soroban-abacus-flashcards/commit/b443ee9cdcd9fcb7674845d8c92f7c338ad98dea))
|
||||
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](https://github.com/antialias/soroban-abacus-flashcards/commit/6ae4d13dc784a87f85206c6ff6d005e5b23b678c))
|
||||
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](https://github.com/antialias/soroban-abacus-flashcards/commit/cae335958751c27684bfb10c8e2e526b460954ed))
|
||||
* **rithmomachia:** add helpful error messages for failed captures ([b172440](https://github.com/antialias/soroban-abacus-flashcards/commit/b172440a41e958ced98903bb8f4c2e4b423e1356))
|
||||
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](https://github.com/antialias/soroban-abacus-flashcards/commit/d42bcff0d922895549c1c12f8e02a3ae6d53425a))
|
||||
* **rithmomachia:** Add interactive playing guide modal ([3121d82](https://github.com/antialias/soroban-abacus-flashcards/commit/3121d8240a567817f5f205a4ef4a788fcf451f71))
|
||||
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](https://github.com/antialias/soroban-abacus-flashcards/commit/82d89131f00517f162ec496397cb390f9ecfc52e))
|
||||
* **rithmomachia:** add ratio capture example to guide ([9150b0c](https://github.com/antialias/soroban-abacus-flashcards/commit/9150b0c678ce7104fe984ee0fc93748b43a245f4))
|
||||
* **rithmomachia:** add standalone guide page route ([3fcc79f](https://github.com/antialias/soroban-abacus-flashcards/commit/3fcc79fe9eae11d4bd3a724c1b1f7d086e7cae81))
|
||||
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](https://github.com/antialias/soroban-abacus-flashcards/commit/74bc3c0dcf8d1ee7084e88a04861a85f9b623809))
|
||||
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](https://github.com/antialias/soroban-abacus-flashcards/commit/1d5f01c966cf1eec9a9c19ee37f1cad93c89df40))
|
||||
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](https://github.com/antialias/soroban-abacus-flashcards/commit/b7fac788292e00c6060a47fdbcca89a7e7fee35c))
|
||||
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](https://github.com/antialias/soroban-abacus-flashcards/commit/4829e41ea13fae2edec10837e65e505929445782))
|
||||
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](https://github.com/antialias/soroban-abacus-flashcards/commit/0a308016e9d6a926c52dbfc5623b60b169d16d03))
|
||||
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](https://github.com/antialias/soroban-abacus-flashcards/commit/f5558563ea93ef7428aa220c2e15e3f02711420f))
|
||||
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](https://github.com/antialias/soroban-abacus-flashcards/commit/55aff829f4c284e8cfe6d471c0821575928b93bc))
|
||||
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](https://github.com/antialias/soroban-abacus-flashcards/commit/9fde1ef9e703e26b2450128155b53fdf2d2e1fe5))
|
||||
* **rithmomachia:** improve roster status notice UX ([e27df45](https://github.com/antialias/soroban-abacus-flashcards/commit/e27df45256147f958ca215f9dd1f4e133e8cf06c))
|
||||
* **rithmomachia:** integrate roster warning into game nav ([8a11594](https://github.com/antialias/soroban-abacus-flashcards/commit/8a11594203fb91faee6cbc4cb74367164ecd6d85))
|
||||
* **rithmomachia:** recreate original guide modal header layout ([2489695](https://github.com/antialias/soroban-abacus-flashcards/commit/24896957d0817758c5f64c0e3473e6a0a343af67))
|
||||
* **rithmomachia:** simplify guide language for clarity ([85cb630](https://github.com/antialias/soroban-abacus-flashcards/commit/85cb630add395a6693ecbbe9c8fc6aaf8c47be29))
|
||||
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](https://github.com/antialias/soroban-abacus-flashcards/commit/be2a00e8b366b5606525309b4c7813f5c35c7f7c))
|
||||
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](https://github.com/antialias/soroban-abacus-flashcards/commit/08c97620f5e694b8526c448c44d265e6dd1fe1eb))
|
||||
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](https://github.com/antialias/soroban-abacus-flashcards/commit/0769eaaa1dc238b901e3a7cfe0486e6122d5eda9))
|
||||
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](https://github.com/antialias/soroban-abacus-flashcards/commit/976a7de949c22842f4b6da3ced990f502a1c2733))
|
||||
* **room-share:** add QR code button for easy mobile joining ([349290a](https://github.com/antialias/soroban-abacus-flashcards/commit/349290ac6a411651686b64d2e6b540083d2df1d9))
|
||||
* show rithmomachia turn in nav ([7c89bfe](https://github.com/antialias/soroban-abacus-flashcards/commit/7c89bfef9c60db0e2c46e920500dcc1fbe90d3df))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](https://github.com/antialias/soroban-abacus-flashcards/commit/fbcde2505f7ff2bf3426f3458e480c4548314ba4))
|
||||
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](https://github.com/antialias/soroban-abacus-flashcards/commit/7c1c2d7bebbb9a1acb274d17dd43b6ee5d196f44))
|
||||
* **arcade:** implement optimistic locking in session manager ([71fd66d](https://github.com/antialias/soroban-abacus-flashcards/commit/71fd66d96a3b03650c90f59f6e516aae7dddc345))
|
||||
* **card-sorting:** add border radius to outer card container ([a922eba](https://github.com/antialias/soroban-abacus-flashcards/commit/a922eba73c4656ee941ce4dfb1dc57a62f076570))
|
||||
* **card-sorting:** add debug logging for spring animations ([d42947e](https://github.com/antialias/soroban-abacus-flashcards/commit/d42947eb8d5d3d8298f5d3b3d1644891c268dbb6))
|
||||
* **card-sorting:** add missing gameMode support after hard reset ([a832325](https://github.com/antialias/soroban-abacus-flashcards/commit/a832325debde289d6928c5e6f9c24311c5e079ad))
|
||||
* **card-sorting:** add missing useMemo import ([949d76d](https://github.com/antialias/soroban-abacus-flashcards/commit/949d76d844c786ada8a6373e4abb7f498f6befb9))
|
||||
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](https://github.com/antialias/soroban-abacus-flashcards/commit/84c66feec6b4112b015e1afd95bf33b24b5f6a4f))
|
||||
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](https://github.com/antialias/soroban-abacus-flashcards/commit/829c741e554d1490dd7a5bbc17f2a32f7195dc07))
|
||||
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](https://github.com/antialias/soroban-abacus-flashcards/commit/fc5cf1216fe03edfb7e44afda01192f4b97b4f4e))
|
||||
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](https://github.com/antialias/soroban-abacus-flashcards/commit/4dce16cca46c965199b7e09f8b34bfa221efac33))
|
||||
* **card-sorting:** animate cards from game board to results grid ([17d45fe](https://github.com/antialias/soroban-abacus-flashcards/commit/17d45fe88cd9773f5e550f6ee5a7f0c82cca2023))
|
||||
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](https://github.com/antialias/soroban-abacus-flashcards/commit/d02ab5922c416042d525f54097a6975ae1541586))
|
||||
* **card-sorting:** enable card scaling for spectators ([6b095c3](https://github.com/antialias/soroban-abacus-flashcards/commit/6b095c33830341c46139bc847ddaab3db632265e))
|
||||
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](https://github.com/antialias/soroban-abacus-flashcards/commit/f3f6eca1db30df9e1e34cc4e77a069a6a3954f3d))
|
||||
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](https://github.com/antialias/soroban-abacus-flashcards/commit/ae45298ec48efb29587c0a1c1a7986a72821f3ef))
|
||||
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](https://github.com/antialias/soroban-abacus-flashcards/commit/dc2d94aaa58531ed4f9047e2ca92724d9264643d))
|
||||
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](https://github.com/antialias/soroban-abacus-flashcards/commit/4b4fbfef322ecda06020ad52d4b1788267112460))
|
||||
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](https://github.com/antialias/soroban-abacus-flashcards/commit/5cca279687d8973d25bd9a411a55b632d1c82f63))
|
||||
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](https://github.com/antialias/soroban-abacus-flashcards/commit/79c94699fa1cc2a2886e3ab1addc5fcd975602f5))
|
||||
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](https://github.com/antialias/soroban-abacus-flashcards/commit/170abed2318432f309de40692f6092bb4c4a1a45))
|
||||
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](https://github.com/antialias/soroban-abacus-flashcards/commit/275cc62a523d9e849f2162001141b6d75ae0925e))
|
||||
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](https://github.com/antialias/soroban-abacus-flashcards/commit/f3687ed236eff4ebe61699ec02909024c7086fb5))
|
||||
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](https://github.com/antialias/soroban-abacus-flashcards/commit/51368c6ec59d5447ce2875c5e1181dec97fd509d))
|
||||
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](https://github.com/antialias/soroban-abacus-flashcards/commit/0d8af09517534f1e1cf1f57160391d465a279d76))
|
||||
* **card-sorting:** preserve rotation when starting drag ([3364144](https://github.com/antialias/soroban-abacus-flashcards/commit/3364144fb6212934b6ad6d63ac6e7b78b436b258))
|
||||
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](https://github.com/antialias/soroban-abacus-flashcards/commit/a0b14f87e9c5b32fcbb685da4e70c563f70ed91a))
|
||||
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](https://github.com/antialias/soroban-abacus-flashcards/commit/bd014bec4ffa12bcd8f4a4e84ff51203c90c1f1d))
|
||||
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](https://github.com/antialias/soroban-abacus-flashcards/commit/34785f466faaa6b9f2958df786af88561fa80b06))
|
||||
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](https://github.com/antialias/soroban-abacus-flashcards/commit/627b873382eaa76ad16477280d10451cf2951e1a))
|
||||
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](https://github.com/antialias/soroban-abacus-flashcards/commit/564a00f82b6ca6aa8a2c0586ca49fc42d44991a8))
|
||||
* **card-sorting:** prevent replaying own movements from server ([308168a](https://github.com/antialias/soroban-abacus-flashcards/commit/308168a7fb51013b0851e98b161ba1a1a3e39fbb))
|
||||
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](https://github.com/antialias/soroban-abacus-flashcards/commit/30953b8c4a3cf147f980455818f9ce8eea07837c))
|
||||
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](https://github.com/antialias/soroban-abacus-flashcards/commit/8aff60ce3f8d302ce5c1bde7cb773e63064c36b7))
|
||||
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](https://github.com/antialias/soroban-abacus-flashcards/commit/a44aa5a4c2d84cab7cf0bbf87485bb61548fdeb2))
|
||||
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](https://github.com/antialias/soroban-abacus-flashcards/commit/15c53ea4eb4abb824eb0360fb645b1f3e455578e))
|
||||
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](https://github.com/antialias/soroban-abacus-flashcards/commit/f5fb4d7b76e25286bcdecd017894ff2d78b31963))
|
||||
* **card-sorting:** show only active players in team members section ([fa9f1a5](https://github.com/antialias/soroban-abacus-flashcards/commit/fa9f1a568f3dff2f4e5e7d3e8841b951ef1b7d04))
|
||||
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](https://github.com/antialias/soroban-abacus-flashcards/commit/0eefc332ac2724c54b477301a269915e895db94f))
|
||||
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](https://github.com/antialias/soroban-abacus-flashcards/commit/b0cd194838705bb7bbf21ac9e318eaba491097b2))
|
||||
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](https://github.com/antialias/soroban-abacus-flashcards/commit/cee399ed1513d32d0fff51a6f63898aa861605e1))
|
||||
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](https://github.com/antialias/soroban-abacus-flashcards/commit/f389afa831935e896a626f526cfee378e340a64b))
|
||||
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](https://github.com/antialias/soroban-abacus-flashcards/commit/6972fdf1105b6e854494efe1c4c587e6b6ff32a9))
|
||||
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](https://github.com/antialias/soroban-abacus-flashcards/commit/e1258ee0416010909774694c0b25306b6f30329c))
|
||||
* copy entire packages/core and packages/templates ([0ccada0](https://github.com/antialias/soroban-abacus-flashcards/commit/0ccada0ca783e635f9ae08f33a69c392018ee342))
|
||||
* correct Typst template path in Dockerfile ([4c518de](https://github.com/antialias/soroban-abacus-flashcards/commit/4c518decb7fcc0b519d07680cbfd01c94c23dd41))
|
||||
* delete existing user sessions before creating new ones ([0cced47](https://github.com/antialias/soroban-abacus-flashcards/commit/0cced47a0f414a04371bdb253fc5a43e4d9557be))
|
||||
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](https://github.com/antialias/soroban-abacus-flashcards/commit/4d4d930bd307ce5a405fc5751af6682a9f221f1f))
|
||||
* increase server update debounce to 2000ms for low bandwidth ([633ff12](https://github.com/antialias/soroban-abacus-flashcards/commit/633ff127500c893a215491afa0e6ff814ad553bf))
|
||||
* Integrate threshold input into Point Victory card ([b29bbee](https://github.com/antialias/soroban-abacus-flashcards/commit/b29bbeefcad92be42f7a3ca27ac126db4232ab26))
|
||||
* **qr-button:** improve layout and z-index ([646a422](https://github.com/antialias/soroban-abacus-flashcards/commit/646a4228d0573796b1a429e31bc037411024c0ff))
|
||||
* **qr-button:** increase mini QR code size to 80px ([61ac737](https://github.com/antialias/soroban-abacus-flashcards/commit/61ac7378bdb01132b26bfc265a057c095ea41606))
|
||||
* **qr-button:** increase mini QR code to 84px ([3fae5ea](https://github.com/antialias/soroban-abacus-flashcards/commit/3fae5ea6fa9ebd0f8fe8c9140a027be7f6a041aa))
|
||||
* **qr-button:** make button square and increase QR size ([dc2d466](https://github.com/antialias/soroban-abacus-flashcards/commit/dc2d46663b8e0ec94a1508a57c4f8c2d8ba03506))
|
||||
* **qr-button:** match height of stacked buttons ([81f202d](https://github.com/antialias/soroban-abacus-flashcards/commit/81f202d21556aa430402fda814519adbc8883831))
|
||||
* **rithmomachia:** add missing i18next dependencies ([91154d9](https://github.com/antialias/soroban-abacus-flashcards/commit/91154d93647e59f7e5f96d1db5624a7ec9b1b9ff))
|
||||
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](https://github.com/antialias/soroban-abacus-flashcards/commit/dae615ee72a7ec7d0b235a22c61ebc4af0d8eadb))
|
||||
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](https://github.com/antialias/soroban-abacus-flashcards/commit/cda1126cb0eab6840df89f3a8778d72410298093))
|
||||
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](https://github.com/antialias/soroban-abacus-flashcards/commit/709322373a91c8174d21052d184fa84dd8bda326))
|
||||
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](https://github.com/antialias/soroban-abacus-flashcards/commit/618e56358deb66cba968472f39b8d4e28b4dd211))
|
||||
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](https://github.com/antialias/soroban-abacus-flashcards/commit/aafb64f3e337c6cf925766fe179b91f66c4a040b))
|
||||
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](https://github.com/antialias/soroban-abacus-flashcards/commit/14259a19a9817d0947467faa004d5f43118f8d8d))
|
||||
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](https://github.com/antialias/soroban-abacus-flashcards/commit/4fa20f44cb9758f29d1f1512232be0fdc0b53b3d))
|
||||
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](https://github.com/antialias/soroban-abacus-flashcards/commit/4834ece98e86f2fb00511bb876a5c32c289df0e0))
|
||||
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](https://github.com/antialias/soroban-abacus-flashcards/commit/d0a8fcdea6aa4fdacfee33e183c92923634ee2b7))
|
||||
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](https://github.com/antialias/soroban-abacus-flashcards/commit/a1a0374fac5dce676df5890663b75531589ed93a))
|
||||
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](https://github.com/antialias/soroban-abacus-flashcards/commit/774c6b0ce712b1a77bb684457da9831e6ec91138))
|
||||
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](https://github.com/antialias/soroban-abacus-flashcards/commit/54bfd2fac86be3597d40c67a1235e4c4ed8e2709))
|
||||
* **room-info:** hide Leave Room button when user is alone ([5927f61](https://github.com/antialias/soroban-abacus-flashcards/commit/5927f61c3c34ba583ee45c8cee48a116c1c03071))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* optimize Docker image size to reduce build failures ([9ca3106](https://github.com/antialias/soroban-abacus-flashcards/commit/9ca310636183f4970db925ce8fa368e23645eb02))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **card-sorting:** remove reveal numbers feature ([ea5e3e8](https://github.com/antialias/soroban-abacus-flashcards/commit/ea5e3e838bd6a5b8b38469a70aa92a0e9baba769))
|
||||
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](https://github.com/antialias/soroban-abacus-flashcards/commit/e4df8432b9c4a2055d47833d56b6e9fcf325ca94))
|
||||
* **rithmomachia:** extract guide sections into separate files ([765525d](https://github.com/antialias/soroban-abacus-flashcards/commit/765525dc451897f561f017e444aae892dc27177f))
|
||||
* **rithmomachia:** make setup phase UI more compact ([e55f848](https://github.com/antialias/soroban-abacus-flashcards/commit/e55f848a26092a2b4a5b09c3c255544ea9666f1b))
|
||||
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](https://github.com/antialias/soroban-abacus-flashcards/commit/dfeeb0e0db8b2c4a38198cf71cd918439d6c211b)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
|
||||
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](https://github.com/antialias/soroban-abacus-flashcards/commit/82a5eb2e4bf74f42a183a15f1129e5ec84cc5231))
|
||||
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](https://github.com/antialias/soroban-abacus-flashcards/commit/0471da598d8d591b3f9d63f467cb35f999924c13))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* add database migration guide and playing guide modal spec ([5a29af7](https://github.com/antialias/soroban-abacus-flashcards/commit/5a29af78e27e897ab35273611b79c4b669304f71))
|
||||
* add deployment verification guidelines to prevent false positives ([3d8da23](https://github.com/antialias/soroban-abacus-flashcards/commit/3d8da2348b4e8a227e963791d15dc6718eac5af1))
|
||||
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](https://github.com/antialias/soroban-abacus-flashcards/commit/008ccead0f9c634fe52fd156e6f9a04d6cdd7744))
|
||||
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](https://github.com/antialias/soroban-abacus-flashcards/commit/e3c1f10233cc0924ff96a643c7c4c1f1278de3e3))
|
||||
* update workflow to require manual testing before commits ([0991796](https://github.com/antialias/soroban-abacus-flashcards/commit/0991796f1eccef345f10205e675e4c33d1a62b17))
|
||||
|
||||
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](https://github.com/antialias/soroban-abacus-flashcards/commit/79f7347d4800646378470a7f9aca8e7f2fd5573c))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { useForm } from '@tanstack/react-form'
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { ConfigurationFormWithoutGenerate } from '@/components/ConfigurationFormWithoutGenerate'
|
||||
import { GenerationProgress } from '@/components/GenerationProgress'
|
||||
import { LivePreview } from '@/components/LivePreview'
|
||||
@@ -107,6 +108,7 @@ export default function CreatePage() {
|
||||
const [generationStatus, setGenerationStatus] = useState<GenerationStatus>('idle')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const globalConfig = useAbacusConfig()
|
||||
const t = useTranslations('create.page')
|
||||
|
||||
const form = useForm<FlashcardFormState>({
|
||||
defaultValues: {
|
||||
@@ -184,7 +186,7 @@ export default function CreatePage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWithNav navTitle="Create Flashcards" navEmoji="✨">
|
||||
<PageWithNav navTitle={t('navTitle')} navEmoji="✨">
|
||||
<div className={css({ minHeight: '100vh', bg: 'gray.50' })}>
|
||||
{/* Main Content */}
|
||||
<div className={container({ maxW: '7xl', px: '4', py: '8' })}>
|
||||
@@ -197,7 +199,7 @@ export default function CreatePage() {
|
||||
color: 'gray.900',
|
||||
})}
|
||||
>
|
||||
Create Your Flashcards
|
||||
{t('hero.title')}
|
||||
</h1>
|
||||
<p
|
||||
className={css({
|
||||
@@ -205,7 +207,7 @@ export default function CreatePage() {
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
Configure content and style, preview instantly, then generate your flashcards
|
||||
{t('hero.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,7 +250,7 @@ export default function CreatePage() {
|
||||
color: 'gray.900',
|
||||
})}
|
||||
>
|
||||
🎨 Visual Style
|
||||
{t('style.title')}
|
||||
</h3>
|
||||
<p
|
||||
className={css({
|
||||
@@ -256,7 +258,7 @@ export default function CreatePage() {
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
See changes instantly in the preview
|
||||
{t('style.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -337,12 +339,12 @@ export default function CreatePage() {
|
||||
animation: 'spin 1s linear infinite',
|
||||
})}
|
||||
/>
|
||||
Generating Your Flashcards...
|
||||
{t('generateButton.loading')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={css({ fontSize: 'xl' })}>✨</div>
|
||||
Generate Flashcards
|
||||
{t('generateButton.cta')}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
@@ -374,7 +376,7 @@ export default function CreatePage() {
|
||||
color: 'red.800',
|
||||
})}
|
||||
>
|
||||
Generation Failed
|
||||
{t('error.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p
|
||||
@@ -399,7 +401,7 @@ export default function CreatePage() {
|
||||
_hover: { bg: 'red.700' },
|
||||
})}
|
||||
>
|
||||
Try Again
|
||||
{t('error.retry')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as Switch from '@radix-ui/react-switch'
|
||||
import * as Tabs from '@radix-ui/react-tabs'
|
||||
import type { FormApi } from '@tanstack/react-form'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { FlashcardFormState } from '@/app/create/page'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { grid, hstack, stack } from '../../styled-system/patterns'
|
||||
@@ -18,6 +19,8 @@ interface ConfigurationFormProps {
|
||||
}
|
||||
|
||||
export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProps) {
|
||||
const t = useTranslations('create.form')
|
||||
|
||||
return (
|
||||
<div className={stack({ gap: '6' })}>
|
||||
<div className={stack({ gap: '2' })}>
|
||||
@@ -28,14 +31,14 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
color: 'gray.900',
|
||||
})}
|
||||
>
|
||||
Configuration
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p
|
||||
className={css({
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
Content, layout, and output settings
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -57,8 +60,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
})}
|
||||
>
|
||||
{[
|
||||
{ value: 'content', label: '📝 Content', icon: '🔢' },
|
||||
{ value: 'output', label: '💾 Output', icon: '💾' },
|
||||
{ value: 'content', label: t('tabs.content'), icon: '🔢' },
|
||||
{ value: 'output', label: t('tabs.output'), icon: '💾' },
|
||||
].map((tab) => (
|
||||
<Tabs.Trigger
|
||||
key={tab.value}
|
||||
@@ -90,15 +93,15 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
<Tabs.Content value="content" className={css({ mt: '6' })}>
|
||||
<div className={stack({ gap: '6' })}>
|
||||
<FormField
|
||||
label="Number Range"
|
||||
description="Define which numbers to include (e.g., '0-99' or '1,2,5,10')"
|
||||
label={t('content.range.label')}
|
||||
description={t('content.range.description')}
|
||||
>
|
||||
<form.Field name="range">
|
||||
{(field) => (
|
||||
<input
|
||||
value={field.state.value || ''}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
placeholder="0-99"
|
||||
placeholder={t('content.range.placeholder')}
|
||||
className={inputStyles}
|
||||
/>
|
||||
)}
|
||||
@@ -106,7 +109,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
</FormField>
|
||||
|
||||
<div className={grid({ columns: 2, gap: '4' })}>
|
||||
<FormField label="Step Size" description="For ranges, increment by this amount">
|
||||
<FormField label={t('content.step.label')} description={t('content.step.description')}>
|
||||
<form.Field name="step">
|
||||
{(field) => (
|
||||
<input
|
||||
@@ -120,7 +123,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
</form.Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Shuffle Cards" description="Randomize the order">
|
||||
<FormField label={t('content.shuffle.label')} description={t('content.shuffle.description')}>
|
||||
<form.Field name="shuffle">
|
||||
{(field) => (
|
||||
<SwitchField
|
||||
@@ -137,7 +140,10 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
{/* Output Tab */}
|
||||
<Tabs.Content value="output" className={css({ mt: '6' })}>
|
||||
<div className={stack({ gap: '6' })}>
|
||||
<FormField label="Output Format" description="Choose your preferred file format">
|
||||
<FormField
|
||||
label={t('output.format.label')}
|
||||
description={t('output.format.description')}
|
||||
>
|
||||
<form.Field name="format">
|
||||
{(field) => (
|
||||
<FormatSelectField
|
||||
@@ -173,7 +179,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
color: 'blue.800',
|
||||
})}
|
||||
>
|
||||
📄 PDF Layout Options
|
||||
{t('output.pdf.sectionTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={css({
|
||||
@@ -181,14 +187,14 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
color: 'blue.700',
|
||||
})}
|
||||
>
|
||||
Configure page layout and printing options for your PDF
|
||||
{t('output.pdf.sectionDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: 2, gap: '4' })}>
|
||||
<FormField
|
||||
label="Cards Per Page"
|
||||
description="Number of flashcards on each page"
|
||||
label={t('output.pdf.cardsPerPage.label')}
|
||||
description={t('output.pdf.cardsPerPage.description')}
|
||||
>
|
||||
<form.Field name="cardsPerPage">
|
||||
{(field) => (
|
||||
@@ -198,13 +204,18 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
min={1}
|
||||
max={12}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} cards`}
|
||||
formatValue={(value) =>
|
||||
t('output.pdf.cardsPerPage.value', { count: value })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</form.Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Paper Size" description="Output paper dimensions">
|
||||
<FormField
|
||||
label={t('output.pdf.paperSize.label')}
|
||||
description={t('output.pdf.paperSize.description')}
|
||||
>
|
||||
<form.Field name="paperSize">
|
||||
{(field) => (
|
||||
<SelectField
|
||||
@@ -213,28 +224,32 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
options={[
|
||||
{
|
||||
value: 'us-letter',
|
||||
label: 'US Letter (8.5×11")',
|
||||
label: t('output.pdf.paperSize.options.us-letter'),
|
||||
},
|
||||
{
|
||||
value: 'a4',
|
||||
label: 'A4 (210×297mm)',
|
||||
label: t('output.pdf.paperSize.options.a4'),
|
||||
},
|
||||
{
|
||||
value: 'a3',
|
||||
label: 'A3 (297×420mm)',
|
||||
label: t('output.pdf.paperSize.options.a3'),
|
||||
},
|
||||
{
|
||||
value: 'a5',
|
||||
label: 'A5 (148×210mm)',
|
||||
label: t('output.pdf.paperSize.options.a5'),
|
||||
},
|
||||
]}
|
||||
placeholder={t('shared.selectPlaceholder')}
|
||||
/>
|
||||
)}
|
||||
</form.Field>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<FormField label="Orientation" description="Page layout direction">
|
||||
<FormField
|
||||
label={t('output.pdf.orientation.label')}
|
||||
description={t('output.pdf.orientation.description')}
|
||||
>
|
||||
<form.Field name="orientation">
|
||||
{(field) => (
|
||||
<RadioGroupField
|
||||
@@ -243,13 +258,17 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
options={[
|
||||
{
|
||||
value: 'portrait',
|
||||
label: '📄 Portrait',
|
||||
desc: 'Taller than wide',
|
||||
label: t('output.pdf.orientation.options.portrait.label'),
|
||||
desc: t(
|
||||
'output.pdf.orientation.options.portrait.description'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'landscape',
|
||||
label: '📃 Landscape',
|
||||
desc: 'Wider than tall',
|
||||
label: t('output.pdf.orientation.options.landscape.label'),
|
||||
desc: t(
|
||||
'output.pdf.orientation.options.landscape.description'
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -259,8 +278,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
|
||||
<div className={grid({ columns: 2, gap: '4' })}>
|
||||
<FormField
|
||||
label="Show Cut Marks"
|
||||
description="Add guides for cutting cards"
|
||||
label={t('output.pdf.showCutMarks.label')}
|
||||
description={t('output.pdf.showCutMarks.description')}
|
||||
>
|
||||
<form.Field name="showCutMarks">
|
||||
{(field) => (
|
||||
@@ -273,8 +292,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label="Registration Marks"
|
||||
description="Alignment guides for duplex printing"
|
||||
label={t('output.pdf.showRegistration.label')}
|
||||
description={t('output.pdf.showRegistration.description')}
|
||||
>
|
||||
<form.Field name="showRegistration">
|
||||
{(field) => (
|
||||
@@ -294,8 +313,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
</form.Field>
|
||||
|
||||
<FormField
|
||||
label="Scale Factor"
|
||||
description="Adjust the overall size of flashcards"
|
||||
label={t('output.scaleFactor.label')}
|
||||
description={t('output.scaleFactor.description')}
|
||||
>
|
||||
<form.Field name="scaleFactor">
|
||||
{(field) => (
|
||||
@@ -305,7 +324,11 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
|
||||
min={0.5}
|
||||
max={1.0}
|
||||
step={0.05}
|
||||
formatValue={(value) => `${Math.round(value * 100)}%`}
|
||||
formatValue={(value) =>
|
||||
t('output.scaleFactor.value', {
|
||||
percent: Math.round(value * 100),
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
@@ -4,48 +4,30 @@ import * as Select from '@radix-ui/react-select'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { hstack, stack } from '../../styled-system/patterns'
|
||||
|
||||
interface FormatOption {
|
||||
value: string
|
||||
label: string
|
||||
icon: string
|
||||
description: string
|
||||
}
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
interface FormatSelectFieldProps {
|
||||
value: string
|
||||
onValueChange: (value: string) => void
|
||||
}
|
||||
|
||||
const formatOptions: FormatOption[] = [
|
||||
{
|
||||
value: 'pdf',
|
||||
label: 'PDF',
|
||||
icon: '📄',
|
||||
description: 'Print-ready vector document with layout options',
|
||||
},
|
||||
{
|
||||
value: 'html',
|
||||
label: 'HTML',
|
||||
icon: '🌐',
|
||||
description: 'Interactive web flashcards',
|
||||
},
|
||||
{
|
||||
value: 'svg',
|
||||
label: 'SVG',
|
||||
icon: '🖼️',
|
||||
description: 'Scalable vector images',
|
||||
},
|
||||
{
|
||||
value: 'png',
|
||||
label: 'PNG',
|
||||
icon: '📷',
|
||||
description: 'High-resolution images',
|
||||
},
|
||||
]
|
||||
|
||||
export function FormatSelectField({ value, onValueChange }: FormatSelectFieldProps) {
|
||||
const selectedOption = formatOptions.find((option) => option.value === value) || formatOptions[0]
|
||||
const t = useTranslations('create.form.formatOptions')
|
||||
const formatOptions = (['pdf', 'html', 'svg', 'png'] as const).map((option) => ({
|
||||
value: option,
|
||||
icon:
|
||||
{
|
||||
pdf: '📄',
|
||||
html: '🌐',
|
||||
svg: '🖼️',
|
||||
png: '📷',
|
||||
}[option],
|
||||
label: t(`${option}.label`),
|
||||
description: t(`${option}.description`),
|
||||
}))
|
||||
|
||||
const selectedOption =
|
||||
formatOptions.find((option) => option.value === value) || formatOptions[0]
|
||||
|
||||
return (
|
||||
<Select.Root value={value} onValueChange={onValueChange}>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import * as Progress from '@radix-ui/react-progress'
|
||||
import { CheckCircle, Sparkles, Zap } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { FlashcardFormState } from '@/app/create/page'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { hstack, stack } from '../../styled-system/patterns'
|
||||
@@ -23,35 +24,40 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
const [progress, setProgress] = useState(0)
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const [steps, setSteps] = useState<ProgressStep[]>([])
|
||||
const t = useTranslations('create.progress')
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize steps based on config
|
||||
const generationSteps: ProgressStep[] = [
|
||||
{
|
||||
id: 'validate',
|
||||
label: 'Validating Configuration',
|
||||
description: 'Checking parameters and dependencies',
|
||||
label: t('steps.validate.label'),
|
||||
description: t('steps.validate.description'),
|
||||
icon: <CheckCircle size={20} />,
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'generate',
|
||||
label: 'Generating Soroban Patterns',
|
||||
description: `Creating ${getEstimatedCardCount(config)} flashcard patterns`,
|
||||
label: t('steps.generate.label'),
|
||||
description: t('steps.generate.description', {
|
||||
count: getEstimatedCardCount(config),
|
||||
}),
|
||||
icon: <Sparkles size={20} />,
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'render',
|
||||
label: `Rendering ${config.format?.toUpperCase() || 'PDF'}`,
|
||||
description: 'Converting to your chosen format',
|
||||
label: t('steps.render.label', {
|
||||
format: config.format?.toUpperCase() || t('steps.render.defaultFormat'),
|
||||
}),
|
||||
description: t('steps.render.description'),
|
||||
icon: <Zap size={20} />,
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'finalize',
|
||||
label: 'Finalizing Download',
|
||||
description: 'Preparing your flashcards for download',
|
||||
label: t('steps.finalize.label'),
|
||||
description: t('steps.finalize.description'),
|
||||
icon: <CheckCircle size={20} />,
|
||||
status: 'pending',
|
||||
},
|
||||
@@ -73,7 +79,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
}, 500)
|
||||
|
||||
return () => clearInterval(progressInterval)
|
||||
}, [config])
|
||||
}, [config, t])
|
||||
|
||||
useEffect(() => {
|
||||
// Update step statuses based on current step
|
||||
@@ -87,6 +93,8 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
|
||||
const estimatedTime = getEstimatedTime(config)
|
||||
const currentStepData = steps[currentStep]
|
||||
const funFacts = (t.raw('funFacts') as string[]) || []
|
||||
const funFact = useMemo(() => getFunFact(funFacts), [funFacts])
|
||||
|
||||
return (
|
||||
<div className={stack({ gap: '6' })}>
|
||||
@@ -99,7 +107,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
color: 'gray.900',
|
||||
})}
|
||||
>
|
||||
Generating Your Flashcards
|
||||
{t('title')}
|
||||
</h3>
|
||||
<div
|
||||
className={css({
|
||||
@@ -108,7 +116,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
~{estimatedTime} seconds
|
||||
{t('estimatedTime', { seconds: estimatedTime })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -181,7 +189,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
{Math.round(progress)}% complete
|
||||
{t('percentComplete', { percent: Math.round(progress) })}
|
||||
</span>
|
||||
<span
|
||||
className={css({
|
||||
@@ -190,7 +198,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
color: 'brand.600',
|
||||
})}
|
||||
>
|
||||
Step {currentStep + 1} of {steps.length}
|
||||
{t('stepCount', { current: currentStep + 1, total: steps.length })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -308,7 +316,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
💡 Did you know?
|
||||
{t('funFactTitle')}
|
||||
</h4>
|
||||
<p
|
||||
className={css({
|
||||
@@ -317,7 +325,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
|
||||
lineHeight: 'relaxed',
|
||||
})}
|
||||
>
|
||||
{getFunFact(config)}
|
||||
{funFact}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,17 +363,10 @@ function getEstimatedTime(config: FlashcardFormState): number {
|
||||
return Math.round((baseTime + cardTime) * formatMultiplier)
|
||||
}
|
||||
|
||||
function getFunFact(_config: FlashcardFormState): string {
|
||||
const facts = [
|
||||
'The soroban is a Japanese counting tool that dates back over 400 years!',
|
||||
'Master soroban users can calculate faster than electronic calculators.',
|
||||
'Each bead position on a soroban represents a specific numeric value.',
|
||||
'The word "soroban" comes from ancient Chinese "suanpan" (counting board).',
|
||||
'Soroban training improves mathematical intuition and mental calculation speed.',
|
||||
'Modern soroban competitions feature lightning-fast calculations.',
|
||||
'The soroban method strengthens both logical and creative thinking.',
|
||||
'Japanese students often learn soroban alongside traditional mathematics.',
|
||||
]
|
||||
function getFunFact(facts: string[]): string {
|
||||
if (!Array.isArray(facts) || facts.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return facts[Math.floor(Math.random() * facts.length)]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { AbacusReact } from '@soroban/abacus-react'
|
||||
import { Eye } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { FlashcardFormState } from '@/app/create/page'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { grid, hstack, stack } from '../../styled-system/patterns'
|
||||
@@ -12,12 +13,14 @@ interface LivePreviewProps {
|
||||
}
|
||||
|
||||
export function LivePreview({ config }: LivePreviewProps) {
|
||||
const t = useTranslations('create.preview')
|
||||
// Generate preview numbers directly from config
|
||||
const previewNumbers = useMemo(() => {
|
||||
return getPreviewNumbers(config.range || '1-10')
|
||||
}, [config.range])
|
||||
|
||||
const previewCount = previewNumbers.length
|
||||
const formatLabel = config.format?.toUpperCase() || t('summary.format.default')
|
||||
|
||||
return (
|
||||
<div className={stack({ gap: '6' })}>
|
||||
@@ -30,7 +33,7 @@ export function LivePreview({ config }: LivePreviewProps) {
|
||||
color: 'gray.900',
|
||||
})}
|
||||
>
|
||||
Live Preview
|
||||
{t('title')}
|
||||
</h3>
|
||||
<p
|
||||
className={css({
|
||||
@@ -38,7 +41,7 @@ export function LivePreview({ config }: LivePreviewProps) {
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
See how your flashcards will look
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div className={hstack({ gap: '3', alignItems: 'center' })}>
|
||||
@@ -53,7 +56,7 @@ export function LivePreview({ config }: LivePreviewProps) {
|
||||
rounded: 'full',
|
||||
})}
|
||||
>
|
||||
{previewCount} cards • {config.format?.toUpperCase()}
|
||||
{t('badge', { count: previewCount, format: formatLabel })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,13 +91,25 @@ export function LivePreview({ config }: LivePreviewProps) {
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Configuration Summary
|
||||
{t('summary.title')}
|
||||
</h4>
|
||||
<div className={grid({ columns: { base: 1, md: 2 }, gap: '3' })}>
|
||||
<ConfigItem label="Range" value={config.range || 'Not set'} />
|
||||
<ConfigItem label="Format" value={config.format?.toUpperCase() || 'PDF'} />
|
||||
<ConfigItem label="Cards per page" value={config.cardsPerPage?.toString() || '6'} />
|
||||
<ConfigItem label="Paper size" value={config.paperSize?.toUpperCase() || 'US-LETTER'} />
|
||||
<ConfigItem
|
||||
label={t('summary.range.label')}
|
||||
value={config.range || t('summary.range.empty')}
|
||||
/>
|
||||
<ConfigItem
|
||||
label={t('summary.format.label')}
|
||||
value={config.format?.toUpperCase() || t('summary.format.default')}
|
||||
/>
|
||||
<ConfigItem
|
||||
label={t('summary.cardsPerPage.label')}
|
||||
value={config.cardsPerPage?.toString() || t('summary.cardsPerPage.default')}
|
||||
/>
|
||||
<ConfigItem
|
||||
label={t('summary.paperSize.label')}
|
||||
value={config.paperSize?.toUpperCase() || t('summary.paperSize.default')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Switch from '@radix-ui/react-switch'
|
||||
import { useAbacusDisplay } from '@soroban/abacus-react'
|
||||
import type { FormApi } from '@tanstack/react-form'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { FlashcardFormState } from '@/app/create/page'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { grid, hstack, stack } from '../../styled-system/patterns'
|
||||
@@ -16,6 +17,7 @@ interface StyleControlsProps {
|
||||
|
||||
export function StyleControls({ form }: StyleControlsProps) {
|
||||
const { config, updateConfig } = useAbacusDisplay()
|
||||
const t = useTranslations('create.styleControls')
|
||||
|
||||
// Sync form values with global context
|
||||
useEffect(() => {
|
||||
@@ -26,7 +28,10 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
}, [config, form])
|
||||
return (
|
||||
<div className={stack({ gap: '4' })}>
|
||||
<FormField label="Color Scheme" description="Choose how colors are applied to beads">
|
||||
<FormField
|
||||
label={t('colorScheme.label')}
|
||||
description={t('colorScheme.description')}
|
||||
>
|
||||
<form.Field name="colorScheme">
|
||||
{(field) => (
|
||||
<RadioGroupField
|
||||
@@ -38,23 +43,23 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
options={[
|
||||
{
|
||||
value: 'monochrome',
|
||||
label: 'Monochrome',
|
||||
desc: 'Classic black and white',
|
||||
label: t('colorScheme.options.monochrome.label'),
|
||||
desc: t('colorScheme.options.monochrome.description'),
|
||||
},
|
||||
{
|
||||
value: 'place-value',
|
||||
label: 'Place Value',
|
||||
desc: 'Colors by digit position',
|
||||
label: t('colorScheme.options.place-value.label'),
|
||||
desc: t('colorScheme.options.place-value.description'),
|
||||
},
|
||||
{
|
||||
value: 'heaven-earth',
|
||||
label: 'Heaven-Earth',
|
||||
desc: 'Different colors for 5s and 1s',
|
||||
label: t('colorScheme.options.heaven-earth.label'),
|
||||
desc: t('colorScheme.options.heaven-earth.description'),
|
||||
},
|
||||
{
|
||||
value: 'alternating',
|
||||
label: 'Alternating',
|
||||
desc: 'Alternating column colors',
|
||||
label: t('colorScheme.options.alternating.label'),
|
||||
desc: t('colorScheme.options.alternating.description'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -62,7 +67,7 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
</form.Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Bead Shape" description="Choose the visual style of the beads">
|
||||
<FormField label={t('beadShape.label')} description={t('beadShape.description')}>
|
||||
<form.Field name="beadShape">
|
||||
{(field) => (
|
||||
<RadioGroupField
|
||||
@@ -74,18 +79,18 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
options={[
|
||||
{
|
||||
value: 'diamond',
|
||||
label: '💎 Diamond',
|
||||
desc: 'Realistic 3D appearance',
|
||||
label: t('beadShape.options.diamond.label'),
|
||||
desc: t('beadShape.options.diamond.description'),
|
||||
},
|
||||
{
|
||||
value: 'circle',
|
||||
label: '⭕ Circle',
|
||||
desc: 'Traditional round beads',
|
||||
label: t('beadShape.options.circle.label'),
|
||||
desc: t('beadShape.options.circle.description'),
|
||||
},
|
||||
{
|
||||
value: 'square',
|
||||
label: '⬜ Square',
|
||||
desc: 'Modern geometric style',
|
||||
label: t('beadShape.options.square.label'),
|
||||
desc: t('beadShape.options.square.description'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -94,7 +99,10 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
</FormField>
|
||||
|
||||
<div className={grid({ columns: 1, gap: '4' })}>
|
||||
<FormField label="Colored Numerals" description="Match numeral colors to bead colors">
|
||||
<FormField
|
||||
label={t('coloredNumerals.label')}
|
||||
description={t('coloredNumerals.description')}
|
||||
>
|
||||
<form.Field name="coloredNumerals">
|
||||
{(field) => (
|
||||
<SwitchField
|
||||
@@ -108,7 +116,10 @@ export function StyleControls({ form }: StyleControlsProps) {
|
||||
</form.Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Hide Inactive Beads" description="Show only active beads for clarity">
|
||||
<FormField
|
||||
label={t('hideInactiveBeads.label')}
|
||||
description={t('hideInactiveBeads.description')}
|
||||
>
|
||||
<form.Field name="hideInactiveBeads">
|
||||
{(field) => (
|
||||
<SwitchField
|
||||
|
||||
229
apps/web/src/i18n/locales/create/de.json
Normal file
229
apps/web/src/i18n/locales/create/de.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "Karteikarten erstellen",
|
||||
"hero": {
|
||||
"title": "Erstelle deine Karteikarten",
|
||||
"subtitle": "Konfiguriere Inhalt und Stil, sieh dir sofort eine Vorschau an und generiere dann deine Karteikarten"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 Visueller Stil",
|
||||
"subtitle": "Sieh Änderungen sofort in der Vorschau"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "Karteikarten werden erstellt...",
|
||||
"cta": "Karteikarten generieren"
|
||||
},
|
||||
"error": {
|
||||
"title": "Erstellung fehlgeschlagen",
|
||||
"retry": "Erneut versuchen"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Konfiguration",
|
||||
"subtitle": "Einstellungen für Inhalt, Layout und Ausgabe",
|
||||
"tabs": {
|
||||
"content": "📝 Inhalt",
|
||||
"output": "💾 Ausgabe"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "Zahlenbereich",
|
||||
"description": "Lege fest, welche Zahlen enthalten sind (z. B. '0-99' oder '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "Schrittweite",
|
||||
"description": "Bei Bereichen wird um diesen Wert erhöht"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "Karten mischen",
|
||||
"description": "Reihenfolge zufällig anordnen"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "Ausgabeformat",
|
||||
"description": "Wähle dein bevorzugtes Dateiformat"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 PDF-Layout-Optionen",
|
||||
"sectionDescription": "Konfiguriere Seitenlayout und Druckoptionen für dein PDF",
|
||||
"cardsPerPage": {
|
||||
"label": "Karten pro Seite",
|
||||
"description": "Anzahl der Karteikarten je Seite",
|
||||
"value": "{count, plural, one {{count} Karte} other {{count} Karten}}"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Papierformat",
|
||||
"description": "Ausgabemaße des Papiers",
|
||||
"options": {
|
||||
"us-letter": "US Letter (8.5×11\")",
|
||||
"a4": "A4 (210×297 mm)",
|
||||
"a3": "A3 (297×420 mm)",
|
||||
"a5": "A5 (148×210 mm)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "Ausrichtung",
|
||||
"description": "Ausrichtung des Seitenlayouts",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 Hochformat",
|
||||
"description": "Höher als breit"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 Querformat",
|
||||
"description": "Breiter als hoch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "Schnittmarken anzeigen",
|
||||
"description": "Hilfslinien zum Zuschneiden hinzufügen"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "Passkreuze",
|
||||
"description": "Ausrichtungshilfen für Duplexdruck"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "Skalierungsfaktor",
|
||||
"description": "Gesamtgröße der Karteikarten anpassen",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "Auswählen..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "Druckfertiges Vektordokument mit Layoutoptionen"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "Interaktive Web-Karteikarten"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "Skalierbare Vektorgrafiken"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "Hochauflösende Bilder"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "Farbschema",
|
||||
"description": "Lege fest, wie Farben auf die Perlen angewendet werden",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "Monochrom",
|
||||
"description": "Klassisches Schwarz-Weiß"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "Stellenwert",
|
||||
"description": "Farben nach Stellenwert"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "Himmel-Erde",
|
||||
"description": "Unterschiedliche Farben für Fünfer und Einer"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "Alternierend",
|
||||
"description": "Abwechselnde Spaltenfarben"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "Perlenform",
|
||||
"description": "Bestimme den visuellen Stil der Perlen",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 Diamant",
|
||||
"description": "Realistische 3D-Anmutung"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ Kreis",
|
||||
"description": "Traditionelle runde Perlen"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ Quadrat",
|
||||
"description": "Moderner geometrischer Stil"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "Farbige Ziffern",
|
||||
"description": "Ziffernfarben an die Perlenfarben anpassen"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "Inaktive Perlen ausblenden",
|
||||
"description": "Nur aktive Perlen für bessere Übersicht zeigen"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "Karteikarten werden erstellt",
|
||||
"estimatedTime": "~{seconds} Sekunden",
|
||||
"percentComplete": "{percent}% abgeschlossen",
|
||||
"stepCount": "Schritt {current} von {total}",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "Konfiguration wird geprüft",
|
||||
"description": "Parameter und Abhängigkeiten werden kontrolliert"
|
||||
},
|
||||
"generate": {
|
||||
"label": "Soroban-Muster werden erzeugt",
|
||||
"description": "{count} Kartenmuster werden erstellt"
|
||||
},
|
||||
"render": {
|
||||
"label": "{format} wird gerendert",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "Umwandlung in dein ausgewähltes Format"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "Download wird vorbereitet",
|
||||
"description": "Karteikarten für den Download bereitstellen"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 Schon gewusst?",
|
||||
"funFacts": [
|
||||
"Das Soroban ist ein japanisches Rechenbrett, das über 400 Jahre alt ist!",
|
||||
"Geübte Soroban-Anwender rechnen schneller als elektronische Taschenrechner.",
|
||||
"Jede Perlenposition auf dem Soroban steht für einen bestimmten Zahlenwert.",
|
||||
"Das Wort \"Soroban\" stammt vom chinesischen \"Suanpan\" (Rechenbrett).",
|
||||
"Soroban-Training verbessert Intuition und Geschwindigkeit beim Kopfrechnen.",
|
||||
"Moderne Soroban-Wettbewerbe zeigen blitzschnelle Rechenkünstler.",
|
||||
"Die Soroban-Methode stärkt logisches und kreatives Denken gleichermaßen.",
|
||||
"In Japan lernen Kinder Soroban oft parallel zum Mathematikunterricht."
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "Live-Vorschau",
|
||||
"subtitle": "So werden deine Karteikarten aussehen",
|
||||
"badge": "{count} {count, plural, one {Karte} other {Karten}} • {format}",
|
||||
"summary": {
|
||||
"title": "Konfigurationsübersicht",
|
||||
"range": {
|
||||
"label": "Bereich",
|
||||
"empty": "Nicht festgelegt"
|
||||
},
|
||||
"format": {
|
||||
"label": "Format",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "Karten pro Seite",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Papierformat",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/web/src/i18n/locales/create/en.json
Normal file
229
apps/web/src/i18n/locales/create/en.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "Create Flashcards",
|
||||
"hero": {
|
||||
"title": "Create Your Flashcards",
|
||||
"subtitle": "Configure content and style, preview instantly, then generate your flashcards"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 Visual Style",
|
||||
"subtitle": "See changes instantly in the preview"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "Generating Your Flashcards...",
|
||||
"cta": "Generate Flashcards"
|
||||
},
|
||||
"error": {
|
||||
"title": "Generation Failed",
|
||||
"retry": "Try Again"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Configuration",
|
||||
"subtitle": "Content, layout, and output settings",
|
||||
"tabs": {
|
||||
"content": "📝 Content",
|
||||
"output": "💾 Output"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "Number Range",
|
||||
"description": "Define which numbers to include (e.g., '0-99' or '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "Step Size",
|
||||
"description": "For ranges, increment by this amount"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "Shuffle Cards",
|
||||
"description": "Randomize the order"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "Output Format",
|
||||
"description": "Choose your preferred file format"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 PDF Layout Options",
|
||||
"sectionDescription": "Configure page layout and printing options for your PDF",
|
||||
"cardsPerPage": {
|
||||
"label": "Cards Per Page",
|
||||
"description": "Number of flashcards on each page",
|
||||
"value": "{count, plural, one {{count} card} other {{count} cards}}"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Paper Size",
|
||||
"description": "Output paper dimensions",
|
||||
"options": {
|
||||
"us-letter": "US Letter (8.5×11\")",
|
||||
"a4": "A4 (210×297mm)",
|
||||
"a3": "A3 (297×420mm)",
|
||||
"a5": "A5 (148×210mm)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "Orientation",
|
||||
"description": "Page layout direction",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 Portrait",
|
||||
"description": "Taller than wide"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 Landscape",
|
||||
"description": "Wider than tall"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "Show Cut Marks",
|
||||
"description": "Add guides for cutting cards"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "Registration Marks",
|
||||
"description": "Alignment guides for duplex printing"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "Scale Factor",
|
||||
"description": "Adjust the overall size of flashcards",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "Select..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "Print-ready vector document with layout options"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "Interactive web flashcards"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "Scalable vector images"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "High-resolution images"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "Color Scheme",
|
||||
"description": "Choose how colors are applied to beads",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "Monochrome",
|
||||
"description": "Classic black and white"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "Place Value",
|
||||
"description": "Colors by digit position"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "Heaven-Earth",
|
||||
"description": "Different colors for 5s and 1s"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "Alternating",
|
||||
"description": "Alternating column colors"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "Bead Shape",
|
||||
"description": "Choose the visual style of the beads",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 Diamond",
|
||||
"description": "Realistic 3D appearance"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ Circle",
|
||||
"description": "Traditional round beads"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ Square",
|
||||
"description": "Modern geometric style"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "Colored Numerals",
|
||||
"description": "Match numeral colors to bead colors"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "Hide Inactive Beads",
|
||||
"description": "Show only active beads for clarity"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "Generating Your Flashcards",
|
||||
"estimatedTime": "~{seconds} seconds",
|
||||
"percentComplete": "{percent}% complete",
|
||||
"stepCount": "Step {current} of {total}",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "Validating Configuration",
|
||||
"description": "Checking parameters and dependencies"
|
||||
},
|
||||
"generate": {
|
||||
"label": "Generating Soroban Patterns",
|
||||
"description": "Creating {count} flashcard patterns"
|
||||
},
|
||||
"render": {
|
||||
"label": "Rendering {format}",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "Converting to your chosen format"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "Finalizing Download",
|
||||
"description": "Preparing your flashcards for download"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 Did you know?",
|
||||
"funFacts": [
|
||||
"The soroban is a Japanese counting tool that dates back over 400 years!",
|
||||
"Master soroban users can calculate faster than electronic calculators.",
|
||||
"Each bead position on a soroban represents a specific numeric value.",
|
||||
"The word \"soroban\" comes from ancient Chinese \"suanpan\" (counting board).",
|
||||
"Soroban training improves mathematical intuition and mental calculation speed.",
|
||||
"Modern soroban competitions feature lightning-fast calculations.",
|
||||
"The soroban method strengthens both logical and creative thinking.",
|
||||
"Japanese students often learn soroban alongside traditional mathematics."
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "Live Preview",
|
||||
"subtitle": "See how your flashcards will look",
|
||||
"badge": "{count} {count, plural, one {card} other {cards}} • {format}",
|
||||
"summary": {
|
||||
"title": "Configuration Summary",
|
||||
"range": {
|
||||
"label": "Range",
|
||||
"empty": "Not set"
|
||||
},
|
||||
"format": {
|
||||
"label": "Format",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "Cards per page",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Paper size",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/web/src/i18n/locales/create/es.json
Normal file
229
apps/web/src/i18n/locales/create/es.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "Crear tarjetas didácticas",
|
||||
"hero": {
|
||||
"title": "Crea tus tarjetas didácticas",
|
||||
"subtitle": "Configura el contenido y el estilo, observa la vista previa al instante y luego genera tus tarjetas"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 Estilo visual",
|
||||
"subtitle": "Observa los cambios al instante en la vista previa"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "Generando tus tarjetas...",
|
||||
"cta": "Generar tarjetas"
|
||||
},
|
||||
"error": {
|
||||
"title": "La generación falló",
|
||||
"retry": "Intentar de nuevo"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Configuración",
|
||||
"subtitle": "Ajustes de contenido, diseño y salida",
|
||||
"tabs": {
|
||||
"content": "📝 Contenido",
|
||||
"output": "💾 Salida"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "Rango numérico",
|
||||
"description": "Define qué números se incluirán (p. ej., '0-99' o '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "Tamaño de paso",
|
||||
"description": "Para rangos, aumenta en esta cantidad"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "Mezclar tarjetas",
|
||||
"description": "Aleatorizar el orden"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "Formato de salida",
|
||||
"description": "Elige tu formato de archivo preferido"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 Opciones de diseño PDF",
|
||||
"sectionDescription": "Configura el diseño de página y las opciones de impresión para tu PDF",
|
||||
"cardsPerPage": {
|
||||
"label": "Tarjetas por página",
|
||||
"description": "Cantidad de tarjetas en cada página",
|
||||
"value": "{count, plural, one {{count} tarjeta} other {{count} tarjetas}}"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Tamaño de papel",
|
||||
"description": "Dimensiones del papel de salida",
|
||||
"options": {
|
||||
"us-letter": "US Letter (8.5×11\")",
|
||||
"a4": "A4 (210×297 mm)",
|
||||
"a3": "A3 (297×420 mm)",
|
||||
"a5": "A5 (148×210 mm)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "Orientación",
|
||||
"description": "Dirección del diseño de página",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 Vertical",
|
||||
"description": "Más alto que ancho"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 Horizontal",
|
||||
"description": "Más ancho que alto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "Mostrar guías de corte",
|
||||
"description": "Agregar guías para recortar las tarjetas"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "Marcas de registro",
|
||||
"description": "Guías de alineación para impresión a doble cara"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "Factor de escala",
|
||||
"description": "Ajusta el tamaño general de las tarjetas",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "Seleccionar..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "Documento vectorial listo para imprimir con opciones de diseño"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "Tarjetas web interactivas"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "Imágenes vectoriales escalables"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "Imágenes de alta resolución"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "Esquema de color",
|
||||
"description": "Elige cómo se aplican los colores a las cuentas",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "Monocromático",
|
||||
"description": "Clásico blanco y negro"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "Valor posicional",
|
||||
"description": "Colores según la posición de cada dígito"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "Cielo-Tierra",
|
||||
"description": "Colores distintos para cuentas de cinco y de uno"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "Alternado",
|
||||
"description": "Colores alternados por columna"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "Forma de las cuentas",
|
||||
"description": "Elige el estilo visual de las cuentas",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 Diamante",
|
||||
"description": "Apariencia realista en 3D"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ Círculo",
|
||||
"description": "Cuentas redondas tradicionales"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ Cuadrado",
|
||||
"description": "Estilo geométrico moderno"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "Números coloreados",
|
||||
"description": "Igualar el color de los números con el de las cuentas"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "Ocultar cuentas inactivas",
|
||||
"description": "Mostrar solo las cuentas activas para mayor claridad"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "Generando tus tarjetas",
|
||||
"estimatedTime": "~{seconds} segundos",
|
||||
"percentComplete": "{percent}% completado",
|
||||
"stepCount": "Paso {current} de {total}",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "Validando la configuración",
|
||||
"description": "Comprobando parámetros y dependencias"
|
||||
},
|
||||
"generate": {
|
||||
"label": "Generando patrones de soroban",
|
||||
"description": "Creando {count} patrones de tarjetas"
|
||||
},
|
||||
"render": {
|
||||
"label": "Renderizando {format}",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "Convirtiendo al formato elegido"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "Finalizando la descarga",
|
||||
"description": "Preparando tus tarjetas para descargar"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 ¿Sabías que...?",
|
||||
"funFacts": [
|
||||
"El soroban es una herramienta japonesa de cálculo con más de 400 años de historia.",
|
||||
"Los expertos del soroban pueden calcular más rápido que las calculadoras electrónicas.",
|
||||
"Cada posición de cuenta en el soroban representa un valor numérico específico.",
|
||||
"La palabra \"soroban\" proviene del antiguo chino \"suanpan\" (tablero de conteo).",
|
||||
"El entrenamiento con soroban mejora la intuición matemática y la velocidad mental.",
|
||||
"Las competencias modernas de soroban muestran cálculos a gran velocidad.",
|
||||
"El método soroban fortalece tanto el pensamiento lógico como el creativo.",
|
||||
"En Japón, el soroban suele aprenderse junto con las matemáticas tradicionales."
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "Vista previa en vivo",
|
||||
"subtitle": "Observa cómo se verán tus tarjetas",
|
||||
"badge": "{count} {count, plural, one {tarjeta} other {tarjetas}} • {format}",
|
||||
"summary": {
|
||||
"title": "Resumen de configuración",
|
||||
"range": {
|
||||
"label": "Rango",
|
||||
"empty": "Sin definir"
|
||||
},
|
||||
"format": {
|
||||
"label": "Formato",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "Tarjetas por página",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Tamaño de papel",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/web/src/i18n/locales/create/hi.json
Normal file
229
apps/web/src/i18n/locales/create/hi.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "फ्लैश कार्ड बनाएं",
|
||||
"hero": {
|
||||
"title": "अपने फ्लैश कार्ड बनाएं",
|
||||
"subtitle": "सामग्री और शैली कॉन्फ़िगर करें, तुरंत पूर्वावलोकन देखें और फिर अपने फ्लैश कार्ड जनरेट करें"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 दृश्य शैली",
|
||||
"subtitle": "पूर्वावलोकन में बदलाव तुरंत देखें"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "आपके फ्लैश कार्ड बन रहे हैं...",
|
||||
"cta": "फ्लैश कार्ड जनरेट करें"
|
||||
},
|
||||
"error": {
|
||||
"title": "जनरेशन विफल",
|
||||
"retry": "फिर से प्रयास करें"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "कॉन्फ़िगरेशन",
|
||||
"subtitle": "सामग्री, लेआउट और आउटपुट सेटिंग्स",
|
||||
"tabs": {
|
||||
"content": "📝 सामग्री",
|
||||
"output": "💾 आउटपुट"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "संख्या सीमा",
|
||||
"description": "किन संख्याओं को शामिल करना है (जैसे '0-99' या '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "चरण अंतर",
|
||||
"description": "सीमा के लिए, इतनी मात्रा से वृद्धि करें"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "कार्ड मिलाएँ",
|
||||
"description": "क्रम को यादृच्छिक करें"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "आउटपुट फ़ॉर्मेट",
|
||||
"description": "अपना पसंदीदा फ़ाइल फ़ॉर्मेट चुनें"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 PDF लेआउट विकल्प",
|
||||
"sectionDescription": "अपने PDF के लिए पेज लेआउट और प्रिंट विकल्प कॉन्फ़िगर करें",
|
||||
"cardsPerPage": {
|
||||
"label": "प्रति पृष्ठ कार्ड",
|
||||
"description": "प्रत्येक पृष्ठ पर फ्लैश कार्ड की संख्या",
|
||||
"value": "{count, plural, one {{count} कार्ड} other {{count} कार्ड}}"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "पेपर आकार",
|
||||
"description": "आउटपुट पेपर के आयाम",
|
||||
"options": {
|
||||
"us-letter": "US Letter (8.5×11\")",
|
||||
"a4": "A4 (210×297 मिमी)",
|
||||
"a3": "A3 (297×420 मिमी)",
|
||||
"a5": "A5 (148×210 मिमी)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "अभिविन्यास",
|
||||
"description": "पेज लेआउट की दिशा",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 लंबवत",
|
||||
"description": "ऊँचाई चौड़ाई से अधिक"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 क्षैतिज",
|
||||
"description": "चौड़ाई ऊँचाई से अधिक"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "कट मार्क दिखाएँ",
|
||||
"description": "कार्ड काटने के लिए गाइड जोड़ें"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "रजिस्ट्रेशन मार्क",
|
||||
"description": "दोनों तरफ प्रिंट के लिए संरेखण गाइड"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "स्केल फ़ैक्टर",
|
||||
"description": "फ्लैश कार्ड का समग्र आकार समायोजित करें",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "चुनें..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "लेआउट विकल्पों वाला प्रिंट-रेडी वेक्टर दस्तावेज़"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "इंटरैक्टिव वेब फ्लैश कार्ड"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "स्केलेबल वेक्टर चित्र"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "उच्च-रिज़ॉल्यूशन छवियाँ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "रंग योजना",
|
||||
"description": "निर्धारित करें कि रंग मोतियों पर कैसे लागू हों",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "मोनोक्रोम",
|
||||
"description": "क्लासिक काला और सफेद"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "स्थान-मूल्य",
|
||||
"description": "प्रत्येक अंक की स्थिति के अनुसार रंग"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "स्वर्ग-पृथ्वी",
|
||||
"description": "पाँच और एक की मोतियों के लिए अलग रंग"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "बारी-बारी",
|
||||
"description": "हर स्तंभ में वैकल्पिक रंग"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "मोती का आकार",
|
||||
"description": "मोती की दृश्य शैली चुनें",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 हीरा",
|
||||
"description": "यथार्थवादी 3D रूप"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ वृत्त",
|
||||
"description": "पारंपरिक गोल मोती"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ वर्ग",
|
||||
"description": "आधुनिक ज्यामितीय शैली"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "रंगीन अंक",
|
||||
"description": "अंकों के रंग को मोतियों से मिलाएँ"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "निष्क्रिय मोती छिपाएँ",
|
||||
"description": "स्पष्टता के लिए केवल सक्रिय मोती दिखाएँ"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "आपके फ्लैश कार्ड तैयार हो रहे हैं",
|
||||
"estimatedTime": "~{seconds} सेकंड",
|
||||
"percentComplete": "{percent}% पूर्ण",
|
||||
"stepCount": "चरण {current} में से {total}",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "कॉन्फ़िगरेशन जाँची जा रही है",
|
||||
"description": "पैरामीटर और निर्भरताएँ सत्यापित की जा रही हैं"
|
||||
},
|
||||
"generate": {
|
||||
"label": "सोरबन पैटर्न बनाए जा रहे हैं",
|
||||
"description": "{count} कार्ड पैटर्न तैयार किए जा रहे हैं"
|
||||
},
|
||||
"render": {
|
||||
"label": "{format} रेंडर हो रहा है",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "आपके चुने हुए फ़ॉर्मेट में परिवर्तित किया जा रहा है"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "डाउनलोड अंतिम रूप में",
|
||||
"description": "डाउनलोड के लिए आपके कार्ड तैयार किए जा रहे हैं"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 क्या आप जानते हैं?",
|
||||
"funFacts": [
|
||||
"सोरबन एक जापानी गणना उपकरण है जो 400 से अधिक वर्ष पुराना है।",
|
||||
"महारथी सोरबन उपयोगकर्ता इलेक्ट्रॉनिक कैलकुलेटर से भी तेज़ गणना कर सकते हैं।",
|
||||
"सोरबन पर प्रत्येक मोती की स्थिति एक विशिष्ट संख्यात्मक मान दर्शाती है।",
|
||||
"\"सोरबन\" शब्द प्राचीन चीनी \"सुआनपान\" (गणना पट्ट) से आया है।",
|
||||
"सोरबन का अभ्यास गणितीय अंतर्ज्ञान और मानसिक गति को बढ़ाता है।",
|
||||
"आधुनिक सोरबन प्रतियोगिताओं में बिजली की गति से गणना होती है।",
|
||||
"सोरबन विधि तार्किक और रचनात्मक दोनों सोच को मजबूत करती है।",
|
||||
"जापान में छात्र अक्सर पारंपरिक गणित के साथ-साथ सोरबन भी सीखते हैं।"
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "लाइव पूर्वावलोकन",
|
||||
"subtitle": "देखें कि आपके फ्लैश कार्ड कैसे दिखेंगे",
|
||||
"badge": "{count} {count, plural, one {कार्ड} other {कार्ड}} • {format}",
|
||||
"summary": {
|
||||
"title": "कॉन्फ़िगरेशन सारांश",
|
||||
"range": {
|
||||
"label": "सीमा",
|
||||
"empty": "निर्धारित नहीं"
|
||||
},
|
||||
"format": {
|
||||
"label": "फ़ॉर्मेट",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "प्रति पृष्ठ कार्ड",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "पेपर आकार",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/web/src/i18n/locales/create/ja.json
Normal file
229
apps/web/src/i18n/locales/create/ja.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "フラッシュカードを作成",
|
||||
"hero": {
|
||||
"title": "フラッシュカードを作りましょう",
|
||||
"subtitle": "内容とスタイルを設定し、すぐにプレビューしてからフラッシュカードを生成しましょう"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 ビジュアルスタイル",
|
||||
"subtitle": "プレビューで変更をすぐに確認"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "フラッシュカードを生成しています...",
|
||||
"cta": "フラッシュカードを生成"
|
||||
},
|
||||
"error": {
|
||||
"title": "生成に失敗しました",
|
||||
"retry": "もう一度試す"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "設定",
|
||||
"subtitle": "コンテンツ・レイアウト・出力の設定",
|
||||
"tabs": {
|
||||
"content": "📝 コンテンツ",
|
||||
"output": "💾 出力"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "数値範囲",
|
||||
"description": "含める数字を指定します(例:'0-99' や '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "刻み幅",
|
||||
"description": "範囲の場合はこの値ずつ増加します"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "カードをシャッフル",
|
||||
"description": "順番をランダムに並べ替えます"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "出力形式",
|
||||
"description": "お好みのファイル形式を選択"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 PDF レイアウトオプション",
|
||||
"sectionDescription": "PDF のページレイアウトと印刷オプションを設定します",
|
||||
"cardsPerPage": {
|
||||
"label": "1ページあたりのカード数",
|
||||
"description": "各ページに配置するカードの数",
|
||||
"value": "{count}枚のカード"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "用紙サイズ",
|
||||
"description": "出力する用紙の寸法",
|
||||
"options": {
|
||||
"us-letter": "USレター (8.5×11\")",
|
||||
"a4": "A4 (210×297mm)",
|
||||
"a3": "A3 (297×420mm)",
|
||||
"a5": "A5 (148×210mm)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "向き",
|
||||
"description": "ページレイアウトの向き",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 縦向き",
|
||||
"description": "高さが幅より長い"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 横向き",
|
||||
"description": "幅が高さより長い"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "カットマークを表示",
|
||||
"description": "カードを切り分けるためのガイドを追加"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "見当合わせのマーク",
|
||||
"description": "両面印刷のための位置合わせガイド"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "縮尺",
|
||||
"description": "カード全体のサイズを調整",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "選択..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "レイアウト設定が可能な印刷向けベクター文書"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "インタラクティブなウェブカード"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "スケーラブルなベクター画像"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "高解像度の画像"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "配色",
|
||||
"description": "ビーズに色を適用する方法を選択",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "モノクロ",
|
||||
"description": "クラシックな白黒"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "位取り",
|
||||
"description": "桁の位置ごとに色分け"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "天地",
|
||||
"description": "五玉と一玉で別の色"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "交互",
|
||||
"description": "列ごとに交互の色"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "ビーズの形",
|
||||
"description": "ビーズの見た目のスタイルを選択",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 ダイヤ",
|
||||
"description": "リアルな立体感"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ 円",
|
||||
"description": "伝統的な丸いビーズ"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ 四角",
|
||||
"description": "モダンな幾何学スタイル"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "数字の色を付ける",
|
||||
"description": "数字の色をビーズの色に合わせる"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "非アクティブなビーズを隠す",
|
||||
"description": "見やすさのためにアクティブなビーズのみ表示"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "フラッシュカードを生成しています",
|
||||
"estimatedTime": "~{seconds}秒",
|
||||
"percentComplete": "{percent}% 完了",
|
||||
"stepCount": "全{total}ステップ中 {current} ステップ目",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "設定を検証しています",
|
||||
"description": "パラメーターと依存関係を確認中"
|
||||
},
|
||||
"generate": {
|
||||
"label": "そろばんパターンを生成",
|
||||
"description": "{count} 件のカードパターンを作成中"
|
||||
},
|
||||
"render": {
|
||||
"label": "{format} をレンダリング",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "選択した形式に変換中"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "ダウンロードを最終準備",
|
||||
"description": "カードをダウンロード用に整えています"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 豆知識",
|
||||
"funFacts": [
|
||||
"そろばんは400年以上の歴史を持つ日本の計算器です。",
|
||||
"熟練したそろばん使いは電子計算機よりも速く計算できます。",
|
||||
"そろばんの各ビーズの位置は特定の数値を表します。",
|
||||
"「そろばん」という言葉は中国古来の「算盤」から来ています。",
|
||||
"そろばんの練習は数学的直感と暗算の速度を高めます。",
|
||||
"現代のそろばん大会では驚異的な速さの計算が披露されます。",
|
||||
"そろばんの学習は論理的思考と創造的思考の両方を鍛えます。",
|
||||
"日本では学校で数学と一緒にそろばんを学ぶことがよくあります。"
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "ライブプレビュー",
|
||||
"subtitle": "フラッシュカードの仕上がりを確認しましょう",
|
||||
"badge": "{count}枚のカード • {format}",
|
||||
"summary": {
|
||||
"title": "設定のサマリー",
|
||||
"range": {
|
||||
"label": "範囲",
|
||||
"empty": "未設定"
|
||||
},
|
||||
"format": {
|
||||
"label": "形式",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "1ページあたりのカード数",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "用紙サイズ",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/web/src/i18n/locales/create/la.json
Normal file
229
apps/web/src/i18n/locales/create/la.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"create": {
|
||||
"page": {
|
||||
"navTitle": "Chartas memoriales crea",
|
||||
"hero": {
|
||||
"title": "Chartas tuas memoriales para",
|
||||
"subtitle": "Argumentum et formam dispone, statim praenotam aspice, deinde chartas tuas gignito"
|
||||
},
|
||||
"style": {
|
||||
"title": "🎨 Stilus visualis",
|
||||
"subtitle": "Mutationes in praenota continuo vide"
|
||||
},
|
||||
"generateButton": {
|
||||
"loading": "Chartae tuae gignuntur...",
|
||||
"cta": "Chartas gignere"
|
||||
},
|
||||
"error": {
|
||||
"title": "Generatio defecit",
|
||||
"retry": "Iterum conare"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Configuratio",
|
||||
"subtitle": "Praecepta de argumento, dispositione et exitu",
|
||||
"tabs": {
|
||||
"content": "📝 Argumentum",
|
||||
"output": "💾 Exitus"
|
||||
},
|
||||
"content": {
|
||||
"range": {
|
||||
"label": "Intervallum numerorum",
|
||||
"description": "Quos numeros includas determina (exempli gratia '0-99' vel '1,2,5,10')",
|
||||
"placeholder": "0-99"
|
||||
},
|
||||
"step": {
|
||||
"label": "Incrementum gradus",
|
||||
"description": "In intervallis hoc quantulum augetur"
|
||||
},
|
||||
"shuffle": {
|
||||
"label": "Chartas misce",
|
||||
"description": "Ordinem fortuito verte"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"format": {
|
||||
"label": "Forma exitus",
|
||||
"description": "Formam tabellae quam mavis selige"
|
||||
},
|
||||
"pdf": {
|
||||
"sectionTitle": "📄 Optiones dispositionis PDF",
|
||||
"sectionDescription": "Paginam et optiones imprimendi pro PDF dispone",
|
||||
"cardsPerPage": {
|
||||
"label": "Chartae per paginam",
|
||||
"description": "Quot chartae singulae paginae habeant",
|
||||
"value": "{count, plural, one {{count} charta} other {{count} chartae}}"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Magnitudo chartae",
|
||||
"description": "Dimensiones chartae exitus",
|
||||
"options": {
|
||||
"us-letter": "US Letter (8.5×11\")",
|
||||
"a4": "A4 (210×297 mm)",
|
||||
"a3": "A3 (297×420 mm)",
|
||||
"a5": "A5 (148×210 mm)"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"label": "Directio",
|
||||
"description": "Quo modo pagina vertatur",
|
||||
"options": {
|
||||
"portrait": {
|
||||
"label": "📄 Verticalis",
|
||||
"description": "Altior quam lata"
|
||||
},
|
||||
"landscape": {
|
||||
"label": "📃 Horizontali",
|
||||
"description": "Latior quam alta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showCutMarks": {
|
||||
"label": "Signa sectionis ostende",
|
||||
"description": "Lineas ad chartas secandas adde"
|
||||
},
|
||||
"showRegistration": {
|
||||
"label": "Signa directionis",
|
||||
"description": "Ducet alignmenti pro impressione duplici"
|
||||
}
|
||||
},
|
||||
"scaleFactor": {
|
||||
"label": "Factor scalae",
|
||||
"description": "Magnitudinem totam chartarum tempera",
|
||||
"value": "{percent}%"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"selectPlaceholder": "Elige..."
|
||||
},
|
||||
"formatOptions": {
|
||||
"pdf": {
|
||||
"label": "PDF",
|
||||
"description": "Documentum vectorium ad imprimendum paratum cum optionibus dispositionis"
|
||||
},
|
||||
"html": {
|
||||
"label": "HTML",
|
||||
"description": "Chartae interretiales interactivae"
|
||||
},
|
||||
"svg": {
|
||||
"label": "SVG",
|
||||
"description": "Imagines vectoriae scalabiles"
|
||||
},
|
||||
"png": {
|
||||
"label": "PNG",
|
||||
"description": "Imagines altae resolutionis"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleControls": {
|
||||
"colorScheme": {
|
||||
"label": "Schema colorum",
|
||||
"description": "Defini quomodo colores in globulos applicantur",
|
||||
"options": {
|
||||
"monochrome": {
|
||||
"label": "Monochromum",
|
||||
"description": "Classica nigro-alba"
|
||||
},
|
||||
"place-value": {
|
||||
"label": "Valor locorum",
|
||||
"description": "Colores secundum ordinem digitorum"
|
||||
},
|
||||
"heaven-earth": {
|
||||
"label": "Caelum-Terra",
|
||||
"description": "Diversi colores globulis quinariis et singularibus"
|
||||
},
|
||||
"alternating": {
|
||||
"label": "Alternans",
|
||||
"description": "Columnae alterno colore"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beadShape": {
|
||||
"label": "Figura globulorum",
|
||||
"description": "Genus aspectus globulorum elige",
|
||||
"options": {
|
||||
"diamond": {
|
||||
"label": "💎 Adamans",
|
||||
"description": "Species quasi tridimensionalis"
|
||||
},
|
||||
"circle": {
|
||||
"label": "⭕ Orbis",
|
||||
"description": "Globuli rotundi traditi"
|
||||
},
|
||||
"square": {
|
||||
"label": "⬜ Quadratum",
|
||||
"description": "Moderna forma geometrica"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coloredNumerals": {
|
||||
"label": "Numeri colorati",
|
||||
"description": "Numerorum colores cum globulis compone"
|
||||
},
|
||||
"hideInactiveBeads": {
|
||||
"label": "Globulos inactivos occulta",
|
||||
"description": "Ut clarius sit, tantum globulos activos ostende"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"title": "Chartae tuae generantur",
|
||||
"estimatedTime": "~{seconds} secundae",
|
||||
"percentComplete": "{percent}% perfectum",
|
||||
"stepCount": "Gradus {current} ex {total}",
|
||||
"steps": {
|
||||
"validate": {
|
||||
"label": "Configuratio probatur",
|
||||
"description": "Parametri et dependentiae recognoscuntur"
|
||||
},
|
||||
"generate": {
|
||||
"label": "Forma soroban paratur",
|
||||
"description": "{count} schemata chartarum creantur"
|
||||
},
|
||||
"render": {
|
||||
"label": "{format} redditur",
|
||||
"defaultFormat": "PDF",
|
||||
"description": "In formam electam convertitur"
|
||||
},
|
||||
"finalize": {
|
||||
"label": "Download paratur",
|
||||
"description": "Chartae ad detrahendum componuntur"
|
||||
}
|
||||
},
|
||||
"funFactTitle": "💡 Scisne?",
|
||||
"funFacts": [
|
||||
"Soroban instrumentum Iaponicum numerandi est quod plus quam annos quadringentos superest.",
|
||||
"Periti soroban citius quam calculatrices electronicae rationes conficiunt.",
|
||||
"Quisque locus globuli in soroban certum numerum repraesentat.",
|
||||
"Vocabulum \"soroban\" ex antiquo Sinico \"suanpan\" (tabula computatoria) derivatur.",
|
||||
"Exercitatio soroban sensum mathematicum et velocitatem mentis auget.",
|
||||
"Certamina soroban hodierna fulmineas computationes ostendunt.",
|
||||
"Methodus soroban tam cogitationem logicam quam creatricem firmat.",
|
||||
"Pueri Iaponici saepe soroban una cum mathematica tradita discunt."
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "Praenotio viva",
|
||||
"subtitle": "Vide quomodo chartae tuae apparebunt",
|
||||
"badge": "{count} {count, plural, one {charta} other {chartae}} • {format}",
|
||||
"summary": {
|
||||
"title": "Summarium configurationis",
|
||||
"range": {
|
||||
"label": "Intervallum",
|
||||
"empty": "Nondum definitum"
|
||||
},
|
||||
"format": {
|
||||
"label": "Forma",
|
||||
"default": "PDF"
|
||||
},
|
||||
"cardsPerPage": {
|
||||
"label": "Chartae per paginam",
|
||||
"default": "6"
|
||||
},
|
||||
"paperSize": {
|
||||
"label": "Magnitudo chartae",
|
||||
"default": "US-LETTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
apps/web/src/i18n/locales/create/messages.ts
Normal file
15
apps/web/src/i18n/locales/create/messages.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import de from './de.json'
|
||||
import en from './en.json'
|
||||
import es from './es.json'
|
||||
import hi from './hi.json'
|
||||
import ja from './ja.json'
|
||||
import la from './la.json'
|
||||
|
||||
export const createMessages = {
|
||||
en: en.create,
|
||||
de: de.create,
|
||||
ja: ja.create,
|
||||
hi: hi.create,
|
||||
es: es.create,
|
||||
la: la.create,
|
||||
} as const
|
||||
@@ -1,4 +1,5 @@
|
||||
import { rithmomachiaMessages } from '@/arcade-games/rithmomachia/messages'
|
||||
import { createMessages } from '@/i18n/locales/create/messages'
|
||||
import { homeMessages } from '@/i18n/locales/home/messages'
|
||||
import { tutorialMessages } from '@/i18n/locales/tutorial/messages'
|
||||
|
||||
@@ -35,6 +36,7 @@ export async function getMessages(locale: Locale) {
|
||||
return mergeMessages(
|
||||
common,
|
||||
{ home: homeMessages[locale] },
|
||||
{ create: createMessages[locale] },
|
||||
{ tutorial: tutorialMessages[locale] },
|
||||
rithmomachiaMessages[locale]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user