diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..846ae5fe --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "lokalise.i18n-ally", + "esbenp.prettier-vscode", + "svelte.svelte-vscode", + "bradlc.vscode-tailwindcss", + "prisma.prisma", + "vunguyentuan.vscode-postcss" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index bdcad3a0..5bf239d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,17 @@ { - "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "html", "markdown", "svelte"] + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "html", "markdown", "svelte"], + "i18n-ally.localesPaths": ["src/i18n"], + "i18n-ally.sortKeys": true, + "i18n-ally.keepFulfilled": false, + "i18n-ally.keystyle": "nested", + "i18n-ally.indent": 4, + "i18n-ally.dirStructure": "file", + "i18n-ally.keysInUse": [ + "general.moderate", + "general.off", + "general.strong", + "general.very-strong", + "general.very-weak", + "general.weak" + ] } diff --git a/README.md b/README.md index 5b77d56e..1cd348f4 100644 --- a/README.md +++ b/README.md @@ -143,3 +143,11 @@ You configure this using environment variables. `HEADER_NAME`: The name of the headers that contains the full name of the user `HEADER_EMAIL`: The name of the headers that contains the email of the user + +## Contributing + +Code contributions are always welcome! If you have something in mind that you would like to work on, please open an issue or comment on an existing issue indicating your interest to make sure someone else isn't already working on it and to discuss any implementation details. Open a PR when you feel that it is ready. You can also open a draft PR as soon as you start work to help track progress. + +### Translations + +Translations are provided by the community and new translations are greatly appreciated. Translations are managed through [Weblate](https://hosted.weblate.org/projects/wishlist/wishlist-web/). With Weblate, you can contribute an entire language, or make suggestions to existing translations. If the language you wish to translate has not been added yet, you can request it [here](https://hosted.weblate.org/new-lang/wishlist/wishlist-web/). The translation strings use ICU Message Syntax which you can reference [here](https://formatjs.github.io/docs/core-concepts/icu-syntax). diff --git a/package.json b/package.json index 7b9255f6..19da48c3 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-en": "^3.0.2", + "@zxcvbn-ts/language-es-es": "^3.0.2", + "@zxcvbn-ts/language-fr": "^3.0.2", "got-scraping": "^4.0.8", "handlebars": "^4.7.8", "lucia": "^3.2.2", @@ -74,6 +76,7 @@ "oslo": "^1.2.1", "prisma": "^6.0.1", "sharp": "^0.33.5", + "svelte-i18n": "^4.0.1", "zod": "^3.23.8" }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccd3347d..d6272671 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: '@zxcvbn-ts/language-en': specifier: ^3.0.2 version: 3.0.2 + '@zxcvbn-ts/language-es-es': + specifier: ^3.0.2 + version: 3.0.2 + '@zxcvbn-ts/language-fr': + specifier: ^3.0.2 + version: 3.0.2 got-scraping: specifier: ^4.0.8 version: 4.0.8 @@ -68,6 +74,9 @@ importers: sharp: specifier: ^0.33.5 version: 0.33.5 + svelte-i18n: + specifier: ^4.0.1 + version: 4.0.1(svelte@5.7.1) zod: specifier: ^3.23.8 version: 3.23.8 @@ -695,108 +704,216 @@ packages: '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.24.0': resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.24.0': resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.24.0': resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.24.0': resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.24.0': resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.24.0': resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.24.0': resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.0': resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.24.0': resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.24.0': resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.24.0': resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.24.0': resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.24.0': resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.24.0': resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.24.0': resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.24.0': resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.24.0': resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.0': resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} @@ -809,30 +926,60 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.0': resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.24.0': resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.24.0': resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.24.0': resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.24.0': resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} @@ -886,6 +1033,21 @@ packages: '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@formatjs/ecma402-abstract@2.0.0': + resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==} + + '@formatjs/fast-memoize@2.2.0': + resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + + '@formatjs/icu-messageformat-parser@2.7.8': + resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==} + + '@formatjs/icu-skeleton-parser@1.8.2': + resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==} + + '@formatjs/intl-localematcher@0.5.4': + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -1637,6 +1799,12 @@ packages: '@zxcvbn-ts/language-en@3.0.2': resolution: {integrity: sha512-Zp+zL+I6Un2Bj0tRXNs6VUBq3Djt+hwTwUz4dkt2qgsQz47U0/XthZ4ULrT/RxjwJRl5LwiaKOOZeOtmixHnjg==} + '@zxcvbn-ts/language-es-es@3.0.2': + resolution: {integrity: sha512-5IbEduFuUtzA6fQtatPo8HIyuIyu6nqsEksz+jXwpFhNUqh+pplFLrpdHbdJ0RENrJ5uk+Fz1pPOTDgBbLPSZQ==} + + '@zxcvbn-ts/language-fr@3.0.2': + resolution: {integrity: sha512-Tj9jS/Z8mNBAD21pn8Mp4O86CPrwImysO1fM3DG+fsfk8W79/MDzqpFDBHiqpu69Uo3LPPctMHEEteakFWt4Qg==} + abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1651,8 +1819,8 @@ packages: peerDependencies: acorn: '>=8.9.0' - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} acorn@8.14.0: @@ -1864,6 +2032,10 @@ packages: resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} engines: {node: '>=10'} + cli-color@2.0.4: + resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} + engines: {node: '>=0.10'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1948,6 +2120,10 @@ packages: resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} engines: {node: '>=18'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + data-uri-to-buffer@5.0.1: resolution: {integrity: sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==} engines: {node: '>= 14'} @@ -2110,6 +2286,25 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.24.0: resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} @@ -2171,6 +2366,10 @@ packages: esm-env@1.2.1: resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2204,9 +2403,15 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2502,6 +2707,9 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + intl-messageformat@10.5.14: + resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -2610,6 +2818,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -2818,6 +3029,9 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + lucia@3.2.2: resolution: {integrity: sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA==} @@ -2847,6 +3061,10 @@ packages: memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + memoizee@0.4.17: + resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} + engines: {node: '>=0.12'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2995,6 +3213,9 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + node-gyp@10.3.1: resolution: {integrity: sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==} engines: {node: ^16.14.0 || >=18.0.0} @@ -3640,6 +3861,13 @@ packages: svelte: optional: true + svelte-i18n@4.0.1: + resolution: {integrity: sha512-jaykGlGT5PUaaq04JWbJREvivlCnALtT+m87Kbm0fxyYHynkQaxQMnIKHLm2WeIuBRoljzwgyvz0Z6/CMwfdmQ==} + engines: {node: '>= 16'} + hasBin: true + peerDependencies: + svelte: ^3 || ^4 || ^5 + svelte-preprocess@6.0.3: resolution: {integrity: sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==} engines: {node: '>= 18.0.0'} @@ -3716,6 +3944,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + timers-ext@0.1.8: + resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} + engines: {node: '>=0.12'} + tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} @@ -3795,6 +4027,9 @@ packages: resolution: {integrity: sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==} engines: {node: '>=16'} + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -4820,75 +5055,144 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.19.12': + optional: true + '@esbuild/aix-ppc64@0.24.0': optional: true + '@esbuild/android-arm64@0.19.12': + optional: true + '@esbuild/android-arm64@0.24.0': optional: true + '@esbuild/android-arm@0.19.12': + optional: true + '@esbuild/android-arm@0.24.0': optional: true + '@esbuild/android-x64@0.19.12': + optional: true + '@esbuild/android-x64@0.24.0': optional: true + '@esbuild/darwin-arm64@0.19.12': + optional: true + '@esbuild/darwin-arm64@0.24.0': optional: true + '@esbuild/darwin-x64@0.19.12': + optional: true + '@esbuild/darwin-x64@0.24.0': optional: true + '@esbuild/freebsd-arm64@0.19.12': + optional: true + '@esbuild/freebsd-arm64@0.24.0': optional: true + '@esbuild/freebsd-x64@0.19.12': + optional: true + '@esbuild/freebsd-x64@0.24.0': optional: true + '@esbuild/linux-arm64@0.19.12': + optional: true + '@esbuild/linux-arm64@0.24.0': optional: true + '@esbuild/linux-arm@0.19.12': + optional: true + '@esbuild/linux-arm@0.24.0': optional: true + '@esbuild/linux-ia32@0.19.12': + optional: true + '@esbuild/linux-ia32@0.24.0': optional: true + '@esbuild/linux-loong64@0.19.12': + optional: true + '@esbuild/linux-loong64@0.24.0': optional: true + '@esbuild/linux-mips64el@0.19.12': + optional: true + '@esbuild/linux-mips64el@0.24.0': optional: true + '@esbuild/linux-ppc64@0.19.12': + optional: true + '@esbuild/linux-ppc64@0.24.0': optional: true + '@esbuild/linux-riscv64@0.19.12': + optional: true + '@esbuild/linux-riscv64@0.24.0': optional: true + '@esbuild/linux-s390x@0.19.12': + optional: true + '@esbuild/linux-s390x@0.24.0': optional: true + '@esbuild/linux-x64@0.19.12': + optional: true + '@esbuild/linux-x64@0.24.0': optional: true + '@esbuild/netbsd-x64@0.19.12': + optional: true + '@esbuild/netbsd-x64@0.24.0': optional: true '@esbuild/openbsd-arm64@0.24.0': optional: true + '@esbuild/openbsd-x64@0.19.12': + optional: true + '@esbuild/openbsd-x64@0.24.0': optional: true + '@esbuild/sunos-x64@0.19.12': + optional: true + '@esbuild/sunos-x64@0.24.0': optional: true + '@esbuild/win32-arm64@0.19.12': + optional: true + '@esbuild/win32-arm64@0.24.0': optional: true + '@esbuild/win32-ia32@0.19.12': + optional: true + '@esbuild/win32-ia32@0.24.0': optional: true + '@esbuild/win32-x64@0.19.12': + optional: true + '@esbuild/win32-x64@0.24.0': optional: true @@ -4944,6 +5248,30 @@ snapshots: '@floating-ui/utils@0.2.8': {} + '@formatjs/ecma402-abstract@2.0.0': + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.8.1 + + '@formatjs/fast-memoize@2.2.0': + dependencies: + tslib: 2.8.1 + + '@formatjs/icu-messageformat-parser@2.7.8': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/icu-skeleton-parser': 1.8.2 + tslib: 2.8.1 + + '@formatjs/icu-skeleton-parser@1.8.2': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + tslib: 2.8.1 + + '@formatjs/intl-localematcher@0.5.4': + dependencies: + tslib: 2.8.1 + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.1': {} @@ -5658,6 +5986,10 @@ snapshots: '@zxcvbn-ts/language-en@3.0.2': {} + '@zxcvbn-ts/language-es-es@3.0.2': {} + + '@zxcvbn-ts/language-fr@3.0.2': {} + abbrev@2.0.0: {} acorn-jsx@5.3.2(acorn@8.14.0): @@ -5668,7 +6000,7 @@ snapshots: dependencies: acorn: 8.14.0 - acorn-walk@8.3.4: + acorn-walk@8.3.3: dependencies: acorn: 8.14.0 @@ -5919,6 +6251,14 @@ snapshots: dependencies: escape-string-regexp: 4.0.0 + cli-color@2.0.4: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + memoizee: 0.4.17 + timers-ext: 0.1.8 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5992,6 +6332,11 @@ snapshots: dependencies: rrweb-cssom: 0.7.1 + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + data-uri-to-buffer@5.0.1: {} data-uri-utils@1.0.8: @@ -6188,6 +6533,57 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.1.0 + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + es6-weak-map@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + esbuild@0.24.0: optionalDependencies: '@esbuild/aix-ppc64': 0.24.0 @@ -6305,6 +6701,13 @@ snapshots: esm-env@1.2.1: {} + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + espree@10.3.0: dependencies: acorn: 8.14.0 @@ -6338,8 +6741,17 @@ snapshots: esutils@2.0.3: {} + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + exponential-backoff@3.1.1: {} + ext@1.7.0: + dependencies: + type: 2.7.3 + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -6656,6 +7068,13 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 + intl-messageformat@10.5.14: + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/fast-memoize': 2.2.0 + '@formatjs/icu-messageformat-parser': 2.7.8 + tslib: 2.8.1 + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -6742,6 +7161,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@2.2.2: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.6 @@ -6936,6 +7357,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-queue@0.1.0: + dependencies: + es5-ext: 0.10.64 + lucia@3.2.2: dependencies: '@oslojs/crypto': 1.0.1 @@ -6982,6 +7407,17 @@ snapshots: memoize-one@6.0.0: {} + memoizee@0.4.17: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.8 + merge2@1.4.1: {} metascraper-amazon@5.45.25: @@ -7130,6 +7566,8 @@ snapshots: neo-async@2.6.2: {} + next-tick@1.1.0: {} + node-gyp@10.3.1: dependencies: env-paths: 2.2.1 @@ -7762,6 +8200,17 @@ snapshots: optionalDependencies: svelte: 5.7.1 + svelte-i18n@4.0.1(svelte@5.7.1): + dependencies: + cli-color: 2.0.4 + deepmerge: 4.3.1 + esbuild: 0.19.12 + estree-walker: 2.0.2 + intl-messageformat: 10.5.14 + sade: 1.8.1 + svelte: 5.7.1 + tiny-glob: 0.2.9 + svelte-preprocess@6.0.3(@babel/core@7.26.0)(postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.49)(yaml@2.6.1))(postcss@8.4.49)(svelte@5.7.1)(typescript@5.7.2): dependencies: svelte: 5.7.1 @@ -7851,6 +8300,11 @@ snapshots: dependencies: any-promise: 1.3.0 + timers-ext@0.1.8: + dependencies: + es5-ext: 0.10.64 + next-tick: 1.1.0 + tiny-glob@0.2.9: dependencies: globalyzer: 0.1.0 @@ -7902,7 +8356,7 @@ snapshots: '@tsconfig/node16': 1.0.4 '@types/node': 22.10.1 acorn: 8.14.0 - acorn-walk: 8.3.4 + acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -7923,6 +8377,8 @@ snapshots: type-fest@4.30.0: {} + type@2.7.3: {} + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 diff --git a/src/ambient.d.ts b/src/ambient.d.ts index 05afb5ec..9681f5aa 100644 --- a/src/ambient.d.ts +++ b/src/ambient.d.ts @@ -3,7 +3,7 @@ declare module "@samirrayani/metascraper-shopping" { } type NavItem = { - label: string; + labelKey: string; href: string; icon: string; }; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index fc51d3c3..f1626d29 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,8 +1,16 @@ import { env } from "$env/dynamic/private"; +import { getClosestAvailableLocaleFromHeader } from "$lib/i18n"; import { auth } from "$lib/server/auth"; import type { Handle } from "@sveltejs/kit"; +import { locale } from "svelte-i18n"; export const handle: Handle = async ({ event, resolve }) => { + const lang = getClosestAvailableLocaleFromHeader(event.request.headers.get("accept-language")); + + if (lang) { + locale.set(lang); + } + const sessionId = event.cookies.get(auth.sessionCookieName); if (!sessionId) { event.locals.user = null; diff --git a/src/i18n/en.json b/src/i18n/en.json new file mode 100644 index 00000000..8ff88be7 --- /dev/null +++ b/src/i18n/en.json @@ -0,0 +1,287 @@ +{ + "a11y": { + "a-person-looking-at-an-empty-board": "A person looking at an empty board", + "cancel-editing": "cancel editing", + "clear-url-field": "clear url field", + "close": "close", + "decrease-priority": "decrease priority for {name}", + "drag-handle": "drag handle for {name}", + "edit-group-name": "edit group name", + "increase-priority": "increase priority for {name}", + "menu": "menu", + "password-strength": "Password Strength", + "refresh-item-data": "refresh item data", + "remove-user-from-group": "remove {user} from group", + "save-group-name": "save group name", + "toggle-manager": "{isManager, select, true {remove} other {add}} {user} as manager", + "toggle-password-visibility": "toggle password visibility", + "two-people-looking-in-an-empty-box": "Two people looking in an empty box", + "upload-profile-image": "upload profile image" + }, + "admin": { + "about": "About", + "account": "Account", + "actions": "Actions", + "add-member": "Add Member", + "add-remove-manager-message": "Are you sure you want to {isManager, select, true {add} other {remove}} this user as group manager?", + "add-remove-manager-title": "{isManager, select, true {Add} other {Remove}} Manager", + "admin": "Admin", + "administration": "Administration", + "build": "Build: {sha}", + "build-date": "Build Date: {buildDate}", + "claims": "Claims", + "clear-lists": "Clear {groupSpecific, select, true {} other {All}} {claimedOnly, select, true {Claimed Items} other {Lists}}", + "clear-lists-confirmation": "Are you sure you wish to clear {claimedOnly, select, true {claimed items} other {all wishlists}} {groupSpecific, select, true {in this group} other {across all groups}}? This action is irreversible!", + "default-group": "Default Group", + "delete-group-message": "Are you sure you want to delete this group? This action is irreversible!", + "delete-group-title": "Delete Group", + "delete-user": "Delete User", + "delete-user-confirmation": "Are you sure you wish to delete {username}? This action is irreversible!", + "dev": "dev", + "generate-reset-password-link": "Generate Reset Password Link", + "group-name": "Group Name", + "group-settings": "Group Settings", + "groups": "Groups", + "id-field": "User Id: {id}", + "make-admin": "Make Admin", + "manager": "Manager", + "members": "Members", + "password-strength-requirement": "Password Strength Requirement", + "password-strength-warning": "Use caution when changing or disabling this value! If disabled, any password of 1 character or more will be allowed.", + "profile": "Profile", + "public-signup": "Public Signup", + "registry-mode-alert-text": "This group is in Registry Mode. No additional members can be added in this mode. You can get a public link to your list on your list page.", + "remove-admin": "Remove Admin", + "remove-member-message": "Are you sure you want to remove this user from the group?\",", + "remove-member-title": "Remove Member", + "security": "Security", + "select-a-group-option": "Select a group", + "settings": "Settings", + "show-name": "Show Name", + "smtp": "SMTP", + "smtp-from-email": "From Email", + "smtp-from-name": "From Name", + "smtp-host": "Host", + "smtp-port": "Port", + "suggestions": "Suggestions", + "suggestions-approval-required": "Approval Required", + "suggestions-auto-approve": "Auto-Approve", + "suggestions-method": "Method", + "suggestions-surprise-me": "Surprise Me", + "unknown": "unknown", + "user-was-deleted": "{username} was deleted", + "username-field": "Username: {username}", + "users": "Users", + "version": "Version: {version}", + "wishlist-mode": "Wishlist Mode", + "wishlist-mode-alert": "There are other members in this group, you cannot switch the mode until you remove all but one member.", + "wishlist-mode-registry": "Registry" + }, + "app": { + "home": "Home", + "my-claims": "My Claims" + }, + "auth": { + "check-your-email": "Check your email and follow the link to reset your password.", + "click-here-to-login": "Click here to login.", + "confirm-password": "Confirm Password", + "contact-admin": "Please contact the site administrator to reset your password.", + "create-an-account": "Create an account", + "current-password": "Current Password", + "email": "Email", + "enter-email": "Enter your email address and we'll send you a password reset link.", + "forgot-password": "Forgot password?", + "log-in": "Log In", + "name": "Name", + "new-password": "New Password", + "password": "Password", + "password-updated-successfully": "Password updated successfully", + "passwords-must-match": "Passwords must match", + "reset-password": "Reset Password", + "return-to-login": "Return to login", + "self-service-password-reset-unavailable": "Self-service password reset unavailable.", + "sign-in": "Sign in", + "sign-out": "Sign Out", + "sign-out-of-all-devices": "Sign out of all devices?", + "sign-up": "Sign Up", + "signup": "Signup", + "update-password": "Update Password", + "username": "Username", + "your-password-was-reset": "Your password was reset." + }, + "errors": { + "asset-not-found": "Asset not found", + "cannot-delete-default-group": "Cannot Delete Default Group", + "cannot-delete-yourself": "Cannot delete yourself", + "cannot-edit-item-that-you-did-not-create": "Cannot edit item that you did not create", + "cant-find-your-group": "Can't find your group!", + "create-group-unknown-error": "An unknown error occurred while creating the group", + "group-does-not-exist": "Group does not exist", + "group-is-already-active": "Group is already active", + "group-is-not-in-registry-mode-cannot-get-a-public-link": "Group is not in registry mode. Cannot get a public link", + "incorrect-password": "Incorrect password", + "invalid-credentials": "Invalid credentials!", + "invalid-currency-code": "Currency code is invalid. A list of valid currency codes can be found here", + "invalid-email": "Please provide a valid email address.", + "invite-code-invalid": "Invite code is either invalid or already been used", + "invite-failed-to-send": "Invite failed to send: {errorMessage}", + "item-id-must-be-a-number": "Item id must be a number", + "item-invalid-ownership": "Item does not belong to {username}", + "item-name-required": "Item name required", + "item-not-found": "Item not found", + "item-with-id-item-id-not-found": "Item with id {id} not found", + "missing-groupid-from-request-parameters": "Missing groupId from request parameters", + "must-specify-an-item-to-delete": "Must specify an item to delete", + "must-specify-asset-id": "Must specify asset id", + "must-specify-group-name-in-body": "Must specify group name in body", + "must-specify-url-in-query-parameters": "Must specify url in query parameters", + "name-must-not-be-blank": "Name must not be blank", + "no-group-friendly-msg": "If you're seeing this page, it's because you aren't in a group yet! Either request a group to join, or create your own.", + "not-authorized": "Not Authorized", + "one-or-more-items-missing-an-id": "One or more items missing an id", + "password-min-strength": "Password must be at least ''{minStrength}''", + "password-must-not-be-blank": "Password must not be blank", + "price-must-have-a-currency": "Price must have a currency", + "product-information-not-available": "Product information not available", + "public-list-already-exists-for-the-user-and-group": "Public list already exists for the user and group", + "public-list-not-found": "Public list not found", + "reset-token-not-found": "Reset token not found", + "something-went-wrong": "Something went wrong", + "suggestions-are-disabled": "Suggestions are disabled", + "the-page-you-were-looking-for-wasnt-found": "The page you were looking for wasn't found", + "this-instance-is-invite-only": "This instance is invite only", + "unable-to-delete-items": "Unable to delete items", + "unable-to-find-product-information": "Unable to find product information. You can still fill in the details manually.", + "unauthenticated": "Must authenticate first", + "user-already-exists": "User with username or email already exists", + "user-is-not-a-member-of-the-group": "User is not a member of the group", + "user-not-found": "User not found", + "user-not-in-group": "User is not part of this group", + "username-already-in-use": "{username} already in use", + "username-must-not-be-blank": "Username must not be blank", + "username-not-specified": "Username not specified", + "valid-url-not-provided": "Valid url not provided" + }, + "general": { + "add-user": "Add User", + "cancel": "Cancel", + "cannot-delete-default-group-msg": "You cannot delete the default group. Please change the default group before deleting this group.", + "change-group": "Change Group", + "confirm": "Confirm", + "copied": "Copied!", + "copy-to-clipboard": "Copy to clipboard", + "create-group": "Create Group", + "dismiss": "Dismiss", + "enable": "Enable", + "enter-group-name": "Enter Group Name", + "enter-user-email": "Enter the user's email and select a group for the user to join", + "group": "Group", + "group-created-successfully": "Group created successfully!", + "invite": "Invite", + "invite-link": "Invite link", + "invite-sent": "Invite sent!", + "invite-user": "Invite User", + "manage-group": "Manage Group", + "mode": "Mode", + "moderate": "Moderate", + "name-optional": "Name (optional)", + "off": "Off", + "ok": "OK", + "oops": "Oops! Something went wrong.", + "please-confirm": "Please Confirm", + "processing": "Processing…", + "provide-the-name-of-the-group-below": "Provide the name of the group below.", + "remove": "Remove", + "required-field": "required field", + "save": "Save", + "saved": "Saved", + "search": "Search", + "search-for-user": "Search for a user to add to the group", + "select-group": "Select Group", + "select-user-group": "Select a group for the user to join. An invite link will be generated", + "smtp-is-not-enabled": "SMTP is not enabled", + "smtp-not-enabled-implication": "While email setup is not a requirement, users will not be able to reset their passwords via self-service and you will have to manually send out links to reset passwords.", + "strong": "Strong", + "submit": "Submit", + "success": "Success!", + "this-invite-link-is-only-valid-for-one-signup": "This invite link is only valid for one signup", + "update": "Update", + "user": "User", + "user-count": "User Count", + "very-strong": "Very Strong", + "very-weak": "Very Weak", + "weak": "Weak", + "wishlists-cleared": "Wishlists cleared." + }, + "setup": { + "back": "Back", + "complete": "Complete", + "create-your-account": "Create your account", + "first-account-admin": "Your first account will be the administrator. You can always add more admins later.", + "get-started": "Get Started", + "global-settings": "Global Settings", + "global-settings-subtext": "These settings will be applied to all groups that are created. Some settings may be overrided by individual groups.", + "hi": "Hi!", + "invite-users": "Invite Users", + "invite-users-subtext": "If you're ready, invite some additional users! You can always invite users later from the admin or group settings.", + "lets-get-started": "Let's get started", + "next": "Next", + "unable-to-update-password": "Unable to update password", + "welcome-to-wishlist": "Welcome to Wishlist" + }, + "wishes": { + "add-item": "Add Item", + "added-by": "Added by {name}", + "approval-confirmation": "Are you sure you wish to {approve, select, true {approve} other {deny}} this suggestion from {name}", + "approval-needed": "Approval Needed", + "approval-required": "{listOwner} will need to approve your suggestion before it is added to their list.", + "approvals": "Approvals", + "approvals-waiting": "You have {approvalCount, plural, one {# item} other {# items}} awaiting your approval", + "approve": "Approve", + "are-you-sure-you-wish-to-delete-name": "Are you sure you wish to delete {name}?", + "before-you-can-claim-the-item-we-just-need-one-thing-from-you": "Before you can claim the item, we just need one thing from you.", + "claim": "Claim", + "claim-details": "Claim Details", + "claimed": "Claimed", + "claimed-by": "Claimed by {name}", + "claimed-item": "{claimed, select, true {Claimed} other {Unclaimed}} item", + "create": "Create Wish", + "create-for": "Create Wish for {listOwner}", + "default-sort": "Default Sort", + "delete": "Delete", + "deny": "Deny", + "edit": "Edit", + "edit-wish": "Edit Wish", + "filter": "Filter", + "finish": "Finish", + "for": "For {name}", + "hang-tight-gathering-product-data": "Hang tight, gathering product data", + "heads-up": "Heads up!", + "image-url": "Image URL", + "item-approved": "{name} was {approved, select, true {approved} other {denied}}", + "item-name": "Item Name", + "item-url": "Item URL", + "item-was-deleted": "{name} was deleted", + "lists": "Lists", + "my-wishes": "My Wishes", + "no-wishes-yet": "No wishes yet", + "note-placeholder": "i.e. size, color, etc.", + "notes": "Notes", + "nothing-claimed-yet": "Nothing claimed yet", + "price": "Price", + "price-high-to-low": "Price: High to Low", + "price-low-to-high": "Price: Low to High", + "public-url": "Public URL", + "purchased": "Purchased", + "reorder": "Reorder", + "share": "Share List", + "unable-to-update-item-ordering": "Unable to update item ordering", + "unclaim": "Unclaim", + "unclaimed": "Unclaimed", + "updated-success": "Item updated successfully", + "upload-image": "Upload Image", + "url-placeholder": "Enter a URL to fetch the item data", + "view-wishes": "View Wishes", + "wishes-for": "{listOwner}'s Wishes" + } +} diff --git a/src/i18n/es.json b/src/i18n/es.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/i18n/es.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n/fr.json b/src/i18n/fr.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/i18n/fr.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n/sv.json b/src/i18n/sv.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/i18n/sv.json @@ -0,0 +1 @@ +{} diff --git a/src/lib/components/BackButton.svelte b/src/lib/components/BackButton.svelte index 9f3bb485..93858b71 100644 --- a/src/lib/components/BackButton.svelte +++ b/src/lib/components/BackButton.svelte @@ -2,12 +2,13 @@ import { afterNavigate } from "$app/navigation"; import { page } from "$app/stores"; import logo from "$lib/assets/logo.png"; + import { t } from "svelte-i18n"; interface Props { label?: string; } - let { label = "Back" }: Props = $props(); + let { label = $t("setup.back") }: Props = $props(); const disabledUrls = [ "/login", "/signup", @@ -34,7 +35,7 @@ Wishlist {:else} - diff --git a/src/lib/components/CreateAccountForm.svelte b/src/lib/components/CreateAccountForm.svelte index f0825c58..214a4b56 100644 --- a/src/lib/components/CreateAccountForm.svelte +++ b/src/lib/components/CreateAccountForm.svelte @@ -1,6 +1,7 @@ {#if $drawerStore.id === "nav"} - + {#await navItems then navItems} + + {/await} {:else} {/if} diff --git a/src/lib/components/PasswordInput.svelte b/src/lib/components/PasswordInput.svelte index eaec6592..7ad8dc7a 100644 --- a/src/lib/components/PasswordInput.svelte +++ b/src/lib/components/PasswordInput.svelte @@ -3,6 +3,7 @@ import { loadOptions, meterLabel } from "$lib/zxcvbn"; import { popup, ProgressBar, type PopupSettings } from "@skeletonlabs/skeleton"; import { onMount } from "svelte"; + import { t } from "svelte-i18n"; interface Props { id: string; @@ -68,7 +69,7 @@ />
- Copy to clipboard + {$t("general.copy-to-clipboard")}
{#if copiedVisible} - Copied! + {$t("general.copied")} {/if} diff --git a/src/lib/components/account/ChangePassword.svelte b/src/lib/components/account/ChangePassword.svelte index 4bda0961..7e006619 100644 --- a/src/lib/components/account/ChangePassword.svelte +++ b/src/lib/components/account/ChangePassword.svelte @@ -3,6 +3,7 @@ import { page } from "$app/stores"; import { getToastStore } from "@skeletonlabs/skeleton"; import PasswordInput from "../PasswordInput.svelte"; + import { t } from "svelte-i18n"; const toastStore = getToastStore(); @@ -18,14 +19,14 @@ use:enhance={() => { return async ({ result, update }) => { if (result.type === "success") { - const t = { - message: "Password updated successfully", + const toastSettings = { + message: $t("auth.password-updated-successfully"), autohide: true, timeout: 5000 }; passwordReset.current = ""; - toastStore.trigger(t); + toastStore.trigger(toastSettings); } passwordReset.new = ""; @@ -39,14 +40,14 @@ id="oldpassword" name="oldPassword" autocomplete="current-password" - label="Current Password" + label={$t("auth.current-password")} bind:value={passwordReset.current} />
@@ -56,11 +57,11 @@ id="confirmpassword" name="newPassword" autocomplete="new-password" - label="Confirm Password" + label={$t("auth.confirm-password")} bind:value={passwordReset.confirm} /> {#if passwordReset.new !== passwordReset.confirm} - Passwords must match + {$t("auth.passwords-must-match")} {/if} {#if $page.form?.error && $page.form?.errors}
    @@ -77,7 +78,7 @@ {/if}
diff --git a/src/lib/components/account/EditProfile.svelte b/src/lib/components/account/EditProfile.svelte index 802df58f..f7e8459e 100644 --- a/src/lib/components/account/EditProfile.svelte +++ b/src/lib/components/account/EditProfile.svelte @@ -2,6 +2,7 @@ import { enhance } from "$app/forms"; import { page } from "$app/stores"; import type { User } from "lucia"; + import { t } from "svelte-i18n"; interface Props { user: User; @@ -13,7 +14,7 @@
diff --git a/src/lib/components/admin/Actions/ClearListsButton.svelte b/src/lib/components/admin/Actions/ClearListsButton.svelte index 8d3e20d8..cf959e69 100644 --- a/src/lib/components/admin/Actions/ClearListsButton.svelte +++ b/src/lib/components/admin/Actions/ClearListsButton.svelte @@ -2,6 +2,7 @@ import { invalidateAll } from "$app/navigation"; import { ItemsAPI } from "$lib/api/items"; import { getModalStore, getToastStore, type ModalSettings } from "@skeletonlabs/skeleton"; + import { t } from "svelte-i18n"; interface Props { groupId?: string | undefined; @@ -17,10 +18,10 @@ const handleDelete = async () => { const settings: ModalSettings = { type: "confirm", - title: "Please Confirm", - body: `Are you sure you wish to clear ${claimed ? "claimed items" : "all wishlists"} ${ - groupId ? "in this group" : "across all groups" - }? This action is irreversible!`, + title: $t("general.please-confirm"), + body: $t("admin.clear-lists-confirmation", { + values: { claimedOnly: claimed, groupSpecific: groupId !== undefined } + }), // confirm = TRUE | cancel = FALSE response: async (r: boolean) => { if (r) { @@ -30,26 +31,27 @@ invalidateAll(); toastStore.trigger({ - message: "Wishlists cleared.", + message: $t("general.wishlists-cleared"), autohide: true, timeout: 5000 }); } else { toastStore.trigger({ - message: `Oops! Something went wrong.`, + message: $t("general.oops"), background: "variant-filled-warning", autohide: true, timeout: 5000 }); } } - } + }, + buttonTextCancel: $t("general.cancel"), + buttonTextConfirm: $t("general.confirm") }; modalStore.trigger(settings); }; diff --git a/src/lib/components/admin/Groups.svelte b/src/lib/components/admin/Groups.svelte index 93043f6f..6d417894 100644 --- a/src/lib/components/admin/Groups.svelte +++ b/src/lib/components/admin/Groups.svelte @@ -11,6 +11,7 @@ } from "@skeletonlabs/skeleton"; import Search from "../Search.svelte"; import { GroupsAPI } from "$lib/api/groups"; + import { t } from "svelte-i18n"; type Group = { id: string; @@ -30,7 +31,7 @@ let groupsFiltered: Group[] = $state(groups); let groupData: TableSource = $derived({ - head: ["Name", "User Count"], + head: [$t("auth.name"), $t("general.user-count")], body: tableMapperValues(groupsFiltered, ["name", "userCount"]), meta: tableSourceMapper(groupsFiltered, ["name", "id"]) }); @@ -43,8 +44,8 @@ const createGroup = () => { const settings: ModalSettings = { type: "prompt", - title: "Enter Group Name", - body: "Provide the name of the group below.", + title: $t("general.enter-group-name"), + body: $t("general.provide-the-name-of-the-group-below"), valueAttr: { type: "text", minlength: 3, maxlength: 32, required: true }, // Returns the updated response value response: async (name: string) => { @@ -55,18 +56,18 @@ const group = await groupsAPI.create(name); if (group) { toastStore.trigger({ - message: "Group created successfully!" + message: $t("general.group-created-successfully") }); } else { toastStore.trigger({ - message: "An unknown error occurred while creating the group" + message: $t("errors.create-group-unknown-error") }); } await invalidateAll(); }, // Optionally override the button text - buttonTextCancel: "Cancel", - buttonTextSubmit: "Submit" + buttonTextCancel: $t("general.cancel"), + buttonTextSubmit: $t("general.submit") }; modalStore.trigger(settings); @@ -77,7 +78,7 @@ diff --git a/src/lib/components/admin/InviteUser.svelte b/src/lib/components/admin/InviteUser.svelte index 8fdd047f..876ad2dc 100644 --- a/src/lib/components/admin/InviteUser.svelte +++ b/src/lib/components/admin/InviteUser.svelte @@ -4,6 +4,7 @@ import { page } from "$app/stores"; import type { Group } from "@prisma/client"; import { fade } from "svelte/transition"; + import { t } from "svelte-i18n"; interface Props { config: Config; @@ -29,14 +30,14 @@ let toastConfig: ToastSettings; if (form?.sent) { toastConfig = { - message: "Invite sent!", + message: $t("general.invite-sent"), background: "variant-filled-success", autohide: true, timeout: 3000 }; } else { toastConfig = { - message: `Invite failed to send: ${form?.message} `, + message: $t("errors.invite-failed-to-send", { values: { errorMessage: form?.message } }), background: "variant-filled-error", autohide: true, timeout: 3000 @@ -67,7 +68,8 @@ if (data.email) email = data.email; if (groupId) setTimeout(() => submitButton?.click(), 200); showUrl = true; - } + }, + buttonTextCancel: $t("general.cancel") }); }; @@ -75,7 +77,7 @@
@@ -91,9 +93,9 @@ out:fade > setTimeout(() => (showUrl = false), 1000)}> - Invite link + {$t("general.invite-link")} - This invite link is only valid for one signup + {$t("general.this-invite-link-is-only-valid-for-one-signup")}
{/if} diff --git a/src/lib/components/admin/SMTPAlert.svelte b/src/lib/components/admin/SMTPAlert.svelte index 70605d6e..a99ac73a 100644 --- a/src/lib/components/admin/SMTPAlert.svelte +++ b/src/lib/components/admin/SMTPAlert.svelte @@ -1,5 +1,7 @@ - + diff --git a/src/lib/components/admin/SettingsForm/DefaultGroup.svelte b/src/lib/components/admin/SettingsForm/DefaultGroup.svelte index 8266eb07..e513d076 100644 --- a/src/lib/components/admin/SettingsForm/DefaultGroup.svelte +++ b/src/lib/components/admin/SettingsForm/DefaultGroup.svelte @@ -1,5 +1,7 @@ - + {#if !disabled} - + {/if} diff --git a/src/lib/components/admin/SettingsForm/PublicSignup.svelte b/src/lib/components/admin/SettingsForm/PublicSignup.svelte index 7f851cf1..f140c7ac 100644 --- a/src/lib/components/admin/SettingsForm/PublicSignup.svelte +++ b/src/lib/components/admin/SettingsForm/PublicSignup.svelte @@ -1,6 +1,8 @@ - + diff --git a/src/lib/components/admin/SettingsForm/SMTP.svelte b/src/lib/components/admin/SettingsForm/SMTP.svelte index 73fb1f3c..f12d6f8a 100644 --- a/src/lib/components/admin/SettingsForm/SMTP.svelte +++ b/src/lib/components/admin/SettingsForm/SMTP.svelte @@ -1,6 +1,7 @@ - + {#if enabled}