Merge branch 'main' into refactor/app

This commit is contained in:
mr. m 2025-05-15 08:29:41 +02:00 committed by GitHub
commit da8dfd63bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 948 additions and 371 deletions

37
.editorconfig Normal file
View file

@ -0,0 +1,37 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Default settings for all files
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# Markdown files
[*.{md,markdown}]
trim_trailing_whitespace = false
# YAML files
[*.{yml,yaml}]
indent_size = 2
# JSON files
[*.{json,jsonc}]
indent_size = 2
# TypeScript and JavaScript files
[*.{ts,tsx,js,jsx}]
indent_size = 2
# Astro files
[*.astro]
indent_size = 2
# Configuration files
[{*.config.js,*.config.ts}]
indent_size = 2

View file

@ -21,5 +21,8 @@ jobs:
- name: Install dependencies
run: npm install --no-frozen-lockfile
- name: Run Biome check
run: npx biome check ./src
- name: Build project
run: npm run build

1
.husky/pre-commit Normal file
View file

@ -0,0 +1 @@
npx lint-staged

View file

@ -1,9 +0,0 @@
/** @type {import('prettier').Config} */
export default {
printWidth: 80,
tabWidth: 2,
semi: false,
singleQuote: true,
endOfLine: 'lf',
plugins: ['prettier-plugin-astro', 'prettier-plugin-tailwindcss'],
}

35
biome.json Normal file
View file

@ -0,0 +1,35 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"nursery": {
"useSortedClasses": "info"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 128,
"useEditorconfig": true
},
"files": {
"ignore": [
"node_modules",
".git",
"dist"
]
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}

698
package-lock.json generated
View file

@ -28,14 +28,14 @@
"motion": "^11.13.5",
"postcss": "^8.5.1",
"preact": "^10.26.2",
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.6",
"sharp": "^0.33.5",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"husky": "^9.1.7",
"lint-staged": "^15.2.7",
"wrangler": "^3.94.0"
}
},
@ -843,6 +843,170 @@
"node": ">=6.9.0"
}
},
"node_modules/@biomejs/biome": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
"dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-win32-x64": "1.9.4"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@capsizecss/unpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz",
@ -2753,6 +2917,22 @@
"node": ">=8"
}
},
"node_modules/ansi-escapes": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
"license": "MIT",
"dependencies": {
"environment": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@ -3784,6 +3964,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true,
"license": "MIT",
"dependencies": {
"slice-ansi": "^5.0.0",
"string-width": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@ -3930,6 +4143,13 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true,
"license": "MIT"
},
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@ -4312,6 +4532,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/es-module-lexer": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
@ -4394,6 +4627,30 @@
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/exit-hook": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
@ -4659,6 +4916,19 @@
"node": ">=0.10.0"
}
},
"node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
@ -4959,6 +5229,32 @@
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"license": "BSD-2-Clause"
},
"node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
"license": "MIT",
"bin": {
"husky": "bin.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/import-meta-resolve": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@ -5095,6 +5391,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-wsl": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@ -5227,12 +5536,121 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/lint-staged": {
"version": "15.5.2",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz",
"integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^5.4.1",
"commander": "^13.1.0",
"debug": "^4.4.0",
"execa": "^8.0.1",
"lilconfig": "^3.1.3",
"listr2": "^8.2.5",
"micromatch": "^4.0.8",
"pidtree": "^0.6.0",
"string-argv": "^0.3.2",
"yaml": "^2.7.0"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
},
"engines": {
"node": ">=18.12.0"
},
"funding": {
"url": "https://opencollective.com/lint-staged"
}
},
"node_modules/lint-staged/node_modules/commander": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/listr2": {
"version": "8.3.3",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz",
"integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
"integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/slice-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
"integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@ -5531,6 +5949,13 @@
"integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
"license": "CC0-1.0"
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true,
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -6128,6 +6553,32 @@
"node": ">=10.0.0"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/miniflare": {
"version": "3.20250214.0",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250214.0.tgz",
@ -6396,6 +6847,35 @@
"node": ">=0.10.0"
}
},
"node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npm-run-path/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -6444,6 +6924,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/oniguruma-parser": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.0.tgz",
@ -6626,6 +7122,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidtree": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
"dev": true,
"license": "MIT",
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -6830,6 +7339,8 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@ -6845,6 +7356,8 @@
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@astrojs/compiler": "^2.9.1",
"prettier": "^3.0.0",
@ -6854,84 +7367,6 @@
"node": "^14.15.0 || >=16.0.0"
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
"integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
"license": "MIT",
"engines": {
"node": ">=14.21.3"
},
"peerDependencies": {
"@ianvs/prettier-plugin-sort-imports": "*",
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"@zackad/prettier-plugin-twig": "*",
"prettier": "^3.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
"prettier-plugin-import-sort": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
"prettier-plugin-multiline-arrays": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-sort-imports": "*",
"prettier-plugin-style-order": "*",
"prettier-plugin-svelte": "*"
},
"peerDependenciesMeta": {
"@ianvs/prettier-plugin-sort-imports": {
"optional": true
},
"@prettier/plugin-pug": {
"optional": true
},
"@shopify/prettier-plugin-liquid": {
"optional": true
},
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"@zackad/prettier-plugin-twig": {
"optional": true
},
"prettier-plugin-astro": {
"optional": true
},
"prettier-plugin-css-order": {
"optional": true
},
"prettier-plugin-import-sort": {
"optional": true
},
"prettier-plugin-jsdoc": {
"optional": true
},
"prettier-plugin-marko": {
"optional": true
},
"prettier-plugin-multiline-arrays": {
"optional": true
},
"prettier-plugin-organize-attributes": {
"optional": true
},
"prettier-plugin-organize-imports": {
"optional": true
},
"prettier-plugin-sort-imports": {
"optional": true
},
"prettier-plugin-style-order": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
}
}
},
"node_modules/printable-characters": {
"version": "1.0.42",
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
@ -7248,6 +7683,39 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restructure": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
@ -7325,6 +7793,13 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
"license": "MIT"
},
"node_modules/rollup": {
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
@ -7448,13 +7923,17 @@
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
"integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
"license": "MIT"
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/sass-formatter": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
"integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"suf-log": "^2.5.3"
}
@ -7623,6 +8102,36 @@
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
"license": "MIT"
},
"node_modules/slice-ansi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.0.0",
"is-fullwidth-code-point": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/smol-toml": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz",
@ -7706,6 +8215,16 @@
"integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
"license": "MIT"
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@ -7816,6 +8335,19 @@
"node": ">=8"
}
},
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strnum": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.1.tgz",
@ -7855,6 +8387,8 @@
"resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
"integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"s.color": "0.0.15"
}

View file

@ -8,7 +8,10 @@
"build": "astro check && astro build",
"preview": "astro preview --port 3000",
"wrangler": "wrangler",
"astro": "astro"
"astro": "astro",
"lint": "biome lint ./src",
"format": "biome format ./src --write",
"prepare": "husky"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
@ -31,14 +34,19 @@
"motion": "^11.13.5",
"postcss": "^8.5.1",
"preact": "^10.26.2",
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.6",
"sharp": "^0.33.5",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"husky": "^9.1.7",
"lint-staged": "^15.2.7",
"wrangler": "^3.94.0"
},
"lint-staged": {
"src/**/*.{ts,tsx,astro,js,jsx}": [
"biome check --write ./src"
]
}
}

View file

@ -3,15 +3,7 @@ import { getLocale, getPath } from '~/utils/i18n'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
const {
class: className,
isPrimary,
isAlert,
isBordered,
href,
id,
extra,
} = Astro.props
const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = Astro.props
---
{

View file

@ -19,15 +19,9 @@ const {
},
} = getUI(locale)
const {
title1 = features.title1,
title2 = features.title2,
title3 = features.title3,
} = Astro.props
const { title1 = features.title1, title2 = features.title2, title3 = features.title3 } = Astro.props
const descriptions = Object.values(features.featureTabs).map(
(tab) => tab.description,
)
const descriptions = Object.values(features.featureTabs).map((tab) => tab.description)
---
<section

View file

@ -91,11 +91,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
<button
type="button"
onClick={() => navigatePage(page - 1)}
className={`px-3 py-2 ${
page === 1
? 'pointer-events-none text-gray-400'
: 'text-dark hover:text-gray-600'
}`}
className={`px-3 py-2 ${page === 1 ? 'pointer-events-none text-gray-400' : 'text-dark hover:text-gray-600'}`}
>
&lt;
</button>
@ -113,10 +109,8 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
)
}
return (
<span className="text-sm">
{value
.replace('{totalPages}', totalPages.toString())
.replace('{totalItems}', totalItems.toString())}
<span key={value} className="text-sm">
{value.replace('{totalPages}', totalPages.toString()).replace('{totalItems}', totalItems.toString())}
</span>
)
})}
@ -124,11 +118,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
<button
type="button"
onClick={() => navigatePage(page + 1)}
className={`px-3 py-2 ${
page === totalPages
? 'pointer-events-none text-gray-400'
: 'text-dark hover:text-gray-600'
}`}
className={`px-3 py-2 ${page === totalPages ? 'pointer-events-none text-gray-400' : 'text-dark hover:text-gray-600'}`}
>
&gt;
</button>
@ -155,10 +145,11 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
<button
type="button"
onClick={toggleCreatedSort}
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
className="flex items-center gap-2 px-4 py-2 font-semibold text-md"
>
{mods.sort.lastCreated}
<span
// biome-ignore lint/security/noDangerouslySetInnerHtml: Icons are safe
dangerouslySetInnerHTML={{
__html: getSortIcon(createdSort).html[0],
}}
@ -170,10 +161,11 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
<button
type="button"
onClick={toggleUpdatedSort}
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
className="flex items-center gap-2 px-4 py-2 font-semibold text-md"
>
{mods.sort.lastUpdated}
<span
// biome-ignore lint/security/noDangerouslySetInnerHtml: Icons are safe
dangerouslySetInnerHTML={{
__html: getSortIcon(updatedSort).html[0],
}}
@ -182,7 +174,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
</div>
<div className="flex items-center gap-2 px-4 py-2">
<label htmlFor="limit" className="text-md font-semibold">
<label htmlFor="limit" className="font-semibold text-md">
{mods.sort.perPage}
</label>
<select
@ -217,20 +209,17 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
/>
</div>
<div>
<h2 className="text-lg font-bold">
{mod.name}{' '}
<span className="ml-1 text-sm font-normal">
by @{mod.author}
</span>
<h2 className="font-bold text-lg">
{mod.name} <span className="ml-1 font-normal text-sm">by @{mod.author}</span>
</h2>
<p className="text-sm font-thin">{mod.description}</p>
<p className="font-thin text-sm">{mod.description}</p>
</div>
</a>
))
) : (
<div className="col-span-4 grid place-items-center gap-4 place-self-center px-8 text-center">
<h2 className="text-lg font-bold">{mods.noResults}</h2>
<p className="text-sm font-thin">{mods.noResultsDescription}</p>
<h2 className="font-bold text-lg">{mods.noResults}</h2>
<p className="font-thin text-sm">{mods.noResultsDescription}</p>
</div>
)}
</div>

View file

@ -4,11 +4,7 @@ import { Info } from 'lucide-astro'
import { releaseNotes as releaseNotesData } from '~/release-notes'
import { getLocale, getPath, getUI } from '~/utils/i18n'
import {
type BreakingChange,
type ReleaseNote,
getReleaseNoteFirefoxVersion,
} from '../release-notes'
import { type BreakingChange, type ReleaseNote, getReleaseNoteFirefoxVersion } from '../release-notes'
export type Props = ReleaseNote
const { isTwilight, ...props } = Astro.props
@ -22,16 +18,14 @@ const {
},
} = getUI(locale)
let date
let date: Date | undefined
if (props.date) {
const [day, month, year] = props.date.split('/')
date = new Date(Date.parse(`${year}-${month}-${day}`))
}
const ffVersion = getReleaseNoteFirefoxVersion(props)
const currentReleaseIndex = releaseNotesData.findIndex(
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
)
const currentReleaseIndex = releaseNotesData.findIndex((releaseNote: ReleaseNote) => releaseNote.version === props.version)
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
let compareLink = ''
if (prevReleaseNote && !isTwilight) {

View file

@ -2,13 +2,7 @@
const { gap = 4 } = Astro.props
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import {
faBluesky,
faGithub,
faMastodon,
faReddit,
faXTwitter,
} from '@fortawesome/free-brands-svg-icons'
import { faBluesky, faGithub, faMastodon, faReddit, faXTwitter } from '@fortawesome/free-brands-svg-icons'
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })

View file

@ -1,10 +1,25 @@
---
interface ReleaseInfo {
label?: string
link: string
checksum?: string
}
interface PlatformReleases {
universal?: ReleaseInfo
all?: ReleaseInfo
tarball?: ReleaseInfo
x86_64?: { tarball: ReleaseInfo } | ReleaseInfo
arm64?: ReleaseInfo
flathub?: { all: ReleaseInfo }
}
interface Props {
platform: 'mac' | 'windows' | 'linux'
icon: string[]
title: string
description: string
releases: Record<string, any>
releases: PlatformReleases
}
const { platform, icon, title, description, releases } = Astro.props
@ -32,7 +47,7 @@ import DownloadCard from './ButtonCard.astro'
{
platform === 'linux' ? (
<>
<div>
{releases.flathub && releases.flathub.all.label && <div>
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
<div class="space-y-3">
<DownloadCard
@ -41,8 +56,8 @@ import DownloadCard from './ButtonCard.astro'
variant="flathub"
/>
</div>
</div>
<div>
</div>}
{releases.x86_64 && 'tarball' in releases.x86_64 && <div>
<h4 class="mb-3 text-lg font-medium">Tarball</h4>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
<DownloadCard
@ -53,23 +68,38 @@ import DownloadCard from './ButtonCard.astro'
/>
<DownloadCard
label="ARM64"
href={releases.aarch64.tarball.link}
href={releases.x86_64.tarball.link}
variant="aarch64"
checksum={releases.aarch64.tarball.checksum}
checksum={releases.x86_64.tarball.checksum}
/>
</div>
</div>
</div>}
</>
) : (
<div class="space-y-4">
{Object.entries(releases).map(([variant, releaseNote]) => (
<div class="space-y-3">
{releases.universal && releases.universal.label && (
<DownloadCard
label={releaseNote.label}
href={releaseNote.link}
variant={variant}
checksum={releaseNote.checksum}
label={releases.universal.label}
href={releases.universal.link}
checksum={releases.universal.checksum}
/>
))}
)}
{releases.x86_64 && 'tarball' in releases.x86_64 && releases.x86_64.tarball && releases.x86_64.tarball.label && (
<DownloadCard
label={releases.x86_64.tarball.label}
href={releases.x86_64.tarball.link}
checksum={releases.x86_64.tarball.checksum}
/>
)}
{releases.arm64 && releases.arm64.label && (
<DownloadCard
label={releases.arm64.label}
href={releases.arm64.link}
checksum={releases.arm64.checksum}
/>
)}
</div>
</div>
)
}

View file

@ -70,9 +70,7 @@ export function useModsSearch(mods: ZenTheme[]) {
searchParams.delete('limit')
}
const newUrl = `${window.location.pathname}${
searchParams.toString() ? `?${searchParams.toString()}` : ''
}`
const newUrl = `${window.location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`
if (state.page > 1) {
window.history.pushState({}, '', newUrl)
@ -92,16 +90,14 @@ export function useModsSearch(mods: ZenTheme[]) {
mod.name.toLowerCase().includes(searchTerm) ||
mod.description.toLowerCase().includes(searchTerm) ||
mod.author.toLowerCase().includes(searchTerm) ||
(mod.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ??
false),
(mod.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ?? false),
)
}
// Sort by createdAt if chosen
if (state.createdSort !== 'default') {
filtered.sort((a, b) => {
const diff =
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
const diff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
return state.createdSort === 'asc' ? diff : -diff
})
}
@ -109,8 +105,7 @@ export function useModsSearch(mods: ZenTheme[]) {
// Sort by updatedAt if chosen
if (state.updatedSort !== 'default') {
filtered.sort((a, b) => {
const diff =
new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
const diff = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
return state.updatedSort === 'asc' ? diff : -diff
})
}
@ -131,12 +126,7 @@ export function useModsSearch(mods: ZenTheme[]) {
const toggleCreatedSort = () => {
setState((prev) => ({
...prev,
createdSort:
prev.createdSort === 'default'
? 'asc'
: prev.createdSort === 'asc'
? 'desc'
: 'default',
createdSort: prev.createdSort === 'default' ? 'asc' : prev.createdSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes
}))
}
@ -144,12 +134,7 @@ export function useModsSearch(mods: ZenTheme[]) {
const toggleUpdatedSort = () => {
setState((prev) => ({
...prev,
updatedSort:
prev.updatedSort === 'default'
? 'asc'
: prev.updatedSort === 'asc'
? 'desc'
: 'default',
updatedSort: prev.updatedSort === 'default' ? 'asc' : prev.updatedSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes
}))
}

View file

@ -1,23 +1,23 @@
---
interface Props {
title: string;
description?: string;
ogImage?: string;
isHome?: boolean;
redirect?: string;
title: string
description?: string
ogImage?: string
isHome?: boolean
redirect?: string
}
const { title, description, ogImage, isHome, redirect } = Astro.props;
const { title, description, ogImage, isHome, redirect } = Astro.props
const defaultDescription =
"Zen Browser is built for speed, security, and true privacy. Download now to enjoy a beautifully-designed, distraction-free web experience packed with features.";
const defaultOgImage = "/share-pic.png";
import "@fontsource/bricolage-grotesque/400.css";
import "@fontsource/bricolage-grotesque/500.css";
import "@fontsource/bricolage-grotesque/600.css";
import Footer from "~/components/Footer.astro";
import NavBar from "~/components/NavBar.astro";
import { getLocale } from "~/utils/i18n";
const locale = getLocale(Astro);
'Zen Browser is built for speed, security, and true privacy. Download now to enjoy a beautifully-designed, distraction-free web experience packed with features.'
const defaultOgImage = '/share-pic.png'
import '@fontsource/bricolage-grotesque/400.css'
import '@fontsource/bricolage-grotesque/500.css'
import '@fontsource/bricolage-grotesque/600.css'
import Footer from '~/components/Footer.astro'
import NavBar from '~/components/NavBar.astro'
import { getLocale } from '~/utils/i18n'
const locale = getLocale(Astro)
---
<script is:inline data-cfasync="false">

View file

@ -1,16 +1,16 @@
---
import Button from "~/components/Button.astro";
import Description from "~/components/Description.astro";
import Layout from "~/layouts/Layout.astro";
import { getLocale, getUI } from "~/utils/i18n";
export { getStaticPaths } from "~/utils/i18n";
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro);
const locale = getLocale(Astro)
const {
routes: { about },
layout,
} = getUI(locale);
} = getUI(locale)
---
<Layout

View file

@ -8,12 +8,7 @@ import { getChecksums } from '~/utils/githubChecksums'
import { getLocale, getUI } from '~/utils/i18n'
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import {
faApple,
faGithub,
faLinux,
faWindows,
} from '@fortawesome/free-brands-svg-icons'
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
import { ExternalLink, Lock } from 'lucide-astro'
export { getStaticPaths } from '~/utils/i18n'

View file

@ -10,12 +10,9 @@ const RSS_ENTRY_LIMIT = 20
* Handles the GET request for the `feed.xml` endpoint.
* @returns The RSS feed for the Zen Browser release notes.
*/
export function GET(context: any) {
export function GET(context: { url: URL }) {
// Just in case the release notes array is empty for whatever reason.
const latestDate =
releaseNotes.length > 0
? formatRssDate(releaseNotes[0].date as string)
: new Date()
const latestDate = releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date()
const rssData: RSSOptions = {
title: 'Zen Browser Release Notes',
@ -89,14 +86,8 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
}
content += addReleaseNoteSection(
'⚠️ Breaking changes',
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
)
content += addReleaseNoteSection(
'✓ Fixes',
releaseNote.fixes?.map(fixToReleaseNote),
)
content += addReleaseNoteSection('⚠️ Breaking changes', releaseNote.breakingChanges?.map(breakingChangeToReleaseNote))
content += addReleaseNoteSection('✓ Fixes', releaseNote.fixes?.map(fixToReleaseNote))
content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
content += addReleaseNoteSection('⭐ Features', releaseNote.features)
@ -109,19 +100,17 @@ function addReleaseNoteSection(title: string, items?: string[]): string {
}
let content = `<h2>${title}</h2>`
content += `<ul>`
content += '<ul>'
for (const item of items) {
if (item && item.length > 0) {
content += `<li>${item}</li>`
}
}
content += `</ul>`
content += '</ul>'
return content
}
function fixToReleaseNote(
fix?: Exclude<ReleaseNote['fixes'], undefined>[number],
) {
function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) {
if (typeof fix === 'string') {
return fix
}
@ -137,18 +126,12 @@ function fixToReleaseNote(
return note
}
function breakingChangeToReleaseNote(
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number],
) {
function breakingChangeToReleaseNote(breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number]) {
if (typeof breakingChange === 'string') {
return breakingChange
}
if (
!breakingChange ||
!breakingChange.description ||
breakingChange.description.length === 0
) {
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
return ''
}
@ -156,19 +139,12 @@ function breakingChangeToReleaseNote(
}
function pubDate(date?: Date) {
date ??= new Date()
const newDate = date ?? new Date()
const pieces = date.toString().split(' ')
const pieces = newDate.toString().split(' ')
const offsetTime = pieces[5].match(/[-+]\d{4}/)
const offset = offsetTime ? offsetTime : pieces[5]
const parts = [
pieces[0] + ',',
pieces[2],
pieces[1],
pieces[3],
pieces[4],
offset,
]
const parts = [`${pieces[0]},`, pieces[2], pieces[1], pieces[3], pieces[4], offset]
return parts.join(' ')
}

View file

@ -43,7 +43,7 @@ const dates = {
updatedAt: getLocalizedDate(mod.updatedAt),
}
const locale = getLocale(Astro)
const locale = getLocale(Astro as { params: { locale?: string } })
const {
routes: {

View file

@ -15,7 +15,7 @@ export async function getStaticPaths() {
const i18nPaths = getI18nPaths()
return i18nPaths.flatMap(({ params: { locale } }) => [
...releaseNotes.map((release: any) => ({
...releaseNotes.map((release) => ({
params: { slug: release.version, locale },
props: { ...release },
})),

View file

@ -5,10 +5,7 @@ import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
import Layout from '~/layouts/Layout.astro'
import {
releaseNotes as releaseNotesData,
releaseNotesTwilight,
} from '~/release-notes'
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from '~/release-notes'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'

View file

@ -22,10 +22,7 @@ const {
} = getUI(locale)
// Just redirect to the release notes if we are in a patch version
if (
latestVersion.version.split('.').length > 2 &&
whatsNewText[1] !== latestVersion.version
) {
if (latestVersion.version.split('.').length > 2 && whatsNewText[1] !== latestVersion.version) {
return Astro.redirect(`/release-notes#${latestVersion.version}`)
}
---

View file

@ -26,9 +26,7 @@ export interface ReleaseNote {
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse()
export { default as releaseNotesTwilight } from './release-notes/twilight.json'
export function getReleaseNoteFirefoxVersion(
releaseNote: ReleaseNote,
): string | null {
export function getReleaseNoteFirefoxVersion(releaseNote: ReleaseNote): string | null {
// Check if "firefox" is on the feature list
for (const feature of releaseNote.features || []) {
if (feature.toLowerCase().includes('firefox')) {

View file

@ -31,10 +31,7 @@
"version": "1.0.0-a.2",
"date": "12/07/2024",
"extra": "This release is the second alpha release of the 1.0.0-alpha series. It includes a lot of bug fixes and improvements given the feedback we received from the first alpha release. This release is still not considered stable, but it's a big step towards the first stable release.",
"features": [
"Added support for macOS aaarch64!",
"Some performance improvements"
],
"features": ["Added support for macOS aaarch64!", "Some performance improvements"],
"fixes": [
{
"description": "Fixed rounded corners of browser views for some websites",
@ -49,18 +46,13 @@
"issue": 50
}
],
"breakingChanges": [
"Removed support window's stub installer. It's under development."
]
"breakingChanges": ["Removed support window's stub installer. It's under development."]
},
{
"version": "1.0.0-a.3",
"date": "14/07/2024",
"extra": "This release is the third alpha release of the 1.0.0-alpha series. One big feature of this release is the new workspaces feature. This feature allows you to create different workspaces with different tabs and configurations. This release also includes a lot of bug fixes and improvements.",
"features": [
"Added support for workspaces (Experimental)",
"Better support for macOS aarch64"
],
"features": ["Added support for workspaces (Experimental)", "Better support for macOS aarch64"],
"fixes": [
{
"description": "Fixed subwindows not being displayed correctly",
@ -262,9 +254,7 @@
"issue": 89
}
],
"breakingChanges": [
"Changed the ID for flatpak to io.github.zen_browser.zen"
]
"breakingChanges": ["Changed the ID for flatpak to io.github.zen_browser.zen"]
},
{
"version": "1.0.0-a.11",
@ -310,9 +300,7 @@
"issue": 124
}
],
"breakingChanges": [
"Changed the ID for AppImage to io.github.zen_browser.zen"
]
"breakingChanges": ["Changed the ID for AppImage to io.github.zen_browser.zen"]
},
{
"version": "1.0.0-a.12",
@ -613,10 +601,7 @@
"version": "1.0.0-a.30",
"date": "26/08/2024",
"extra": "This release is the thirtieth alpha release of the 1.0.0-alpha series.",
"features": [
"Added support for 24 more languages!",
"Update installed mods from the browser settings"
],
"features": ["Added support for 24 more languages!", "Update installed mods from the browser settings"],
"fixes": [
{
"description": "Letterboxing option is missing",
@ -904,10 +889,7 @@
"description": "Fixed Mods not being applied to every single window opened"
}
],
"breakingChanges": [
"Removed Galaxy and Dream mods",
"Removed the 'legacy-toolbar' preference"
],
"breakingChanges": ["Removed Galaxy and Dream mods", "Removed the 'legacy-toolbar' preference"],
"themeChanges": [
"Themes will now be able to have string and number values",
"The configuration schema for mods has been updated. All current mods have been updated automatically."
@ -947,11 +929,7 @@
"image": false,
"workflowId": 11020784612,
"extra": "This update is a small patch to fix some issues that weren't addressed in the previous release!",
"features": [
"Moved application menu button to the right",
"Added new shortcuts",
"Collapsed tab sidebar is now smaller"
],
"features": ["Moved application menu button to the right", "Added new shortcuts", "Collapsed tab sidebar is now smaller"],
"fixes": [
{
"description": "Fixed issue with hovering over window control buttons (macOS)"
@ -1155,10 +1133,7 @@
"description": "Fixed about page linking 'global Community' to a Mozilla page"
}
],
"features": [
"About page will now display the Firefox version used",
"Disabled forcing container grouping for workspaces"
]
"features": ["About page will now display the Firefox version used", "Disabled forcing container grouping for workspaces"]
},
{
"version": "1.0.1-a.11",
@ -1299,9 +1274,7 @@
"description": "Fixed sidebar webpanels being in a darker contrast"
}
],
"features": [
"Added a confirmation dialog when the gradient generator has successfully saved the gradient"
]
"features": ["Added a confirmation dialog when the gradient generator has successfully saved the gradient"]
},
{
"version": "1.0.1-a.15",
@ -1347,9 +1320,7 @@
"issue": 2413
}
],
"breakingChanges": [
"Changed the default layout of the customizable UI buttons"
],
"breakingChanges": ["Changed the default layout of the customizable UI buttons"],
"features": [
"Added Zen Glance!",
"Updated to the latest stable version of Firefox (132.0)",
@ -2159,10 +2130,7 @@
"date": "30/01/2025",
"workflowId": 13062083313,
"extra": "Quick fix for a critical bug that was introduced in the previous release.",
"fixes": [
"Fixed the browser not opening when having multiple windows",
"Fixed macos fullscreen having a weird shadow"
]
"fixes": ["Fixed the browser not opening when having multiple windows", "Fixed macos fullscreen having a weird shadow"]
},
{
"version": "1.7.5b",
@ -2221,9 +2189,7 @@
"Fixed opening glance tabs on essentials messing up the sidebar",
"Fixed pinned tabs appearing on normal container after a restart"
],
"features": [
"Tabs can now be dragged into pinned tabs by dragging them into the workspace indicator"
],
"features": ["Tabs can now be dragged into pinned tabs by dragging them into the workspace indicator"],
"workflowId": 13209591935,
"date": "08/02/2025"
},
@ -2315,9 +2281,7 @@
"Fixed pinning a tab adding them to the essentials container",
"Other small fixes for compact mode not animating properly"
],
"features": [
"localhost and http URL will no longer be trimmed in single toolbar layout"
],
"features": ["localhost and http URL will no longer be trimmed in single toolbar layout"],
"workflowId": 13530880093,
"date": "25/02/2025"
},
@ -2731,25 +2695,25 @@
"fixes": [
"Fixed issues with toast notifications not being hidden after a timeout and displaying when they shouldn't",
{
"description": "Fixed macos close buttons being misaligned on collapsed sidebar",
"description": "Fixed MacOS close buttons being misaligned on the collapsed sidebar",
"issue": 7129
},
{
"description": "Fixed the download animation appearing on all the windows",
"description": "Fixed the download animation appearing on all windows",
"issue": 8247
},
{
"description": "When using Tabs on the Right, the Glance controls are on the wrong side",
"description": "Fixed Glance controls appearing on the wrong side when 'Tabs on the Right' was used",
"issue": 7910
},
"Fixed various issues with the urlbar on double toolbar mode",
"Fixed various issues with the URL bar on double toolbar mode",
"Fixed some issues with restoring the previous session"
],
"features": [
"Updated firefox 138.0.3",
"For macos users, dragging tabs and changing the texture for the workspace's background now provides haptic feedback (zen.haptic-feedback.enabled)",
"The 'copy current url' toast now displays a share icon (only on windows and macos)",
"The urlbar now can search through renamed pinned tabs"
"Updated to Firefox 138.0.3",
"For MacOS users, dragging tabs and changing the texture for the workspace's background now provides haptic feedback (zen.haptic-feedback.enabled)",
"The 'copy current url' toast now displays a share icon (only on windows and MacOS)",
"The URL bar can now search through renamed pinned tabs"
],
"workflowId": 15003870178,
"date": "14/05/2025"
@ -2758,10 +2722,7 @@
"version": "1.12.5b",
"image": false,
"extra": "",
"fixes": [
"Fixed a weird shadow with the URL bar.",
"Fixed all tabs button appearing unexpectedly."
],
"fixes": ["Fixed a weird shadow with the URL bar.", "Fixed all tabs button appearing unexpectedly."],
"features": [],
"workflowId": 15024223699,
"date": "14/05/2025"

View file

@ -3,29 +3,25 @@
* Returns a mapping from filename to checksum.
*/
export async function getChecksums() {
const res = await fetch(
'https://api.github.com/repos/zen-browser/desktop/releases/latest',
{
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'zen-browser-checksum-fetcher',
},
},
)
if (!res.ok)
throw new Error('Failed to fetch GitHub release: ' + res.statusText)
})
if (!res.ok) throw new Error(`Failed to fetch GitHub release: ${res.statusText}`)
const data = await res.json()
const body = data.body as string
// Extract the checksum block
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
const checksums: Record<string, string> = {}
if (match && match[1]) {
match[1].split('\n').forEach((line) => {
if (match?.[1]) {
for (const line of match[1].split('\n')) {
const [hash, filename] = line.trim().split(/\s+/, 2)
if (hash && filename) checksums[filename] = hash
})
}
}
return checksums
}

View file

@ -2,76 +2,142 @@ import type { GetStaticPaths } from 'astro'
import { CONSTANT } from '~/constants'
import UI_EN from '~/i18n/en/translation.json'
/**
* Represents the available locales in the application
* @typedef {string} Locale
*/
export type Locale = (typeof locales)[number]
/**
* Generates a localized path by prefixing the locale if necessary
* @param {Locale} [locale] - The current locale
* @returns {function(string): string} A function that transforms paths based on the locale
*/
export const getPath = (locale?: Locale) => (path: string) => {
if (locale && !path.startsWith(`/${locale}`)) {
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
}
return path
}
export const getLocale = (Astro: any) => {
if (Astro.params.locale) {
/**
* Extracts the current locale from Astro's params, defaulting to the default locale
* @param {Object} Astro - Astro's context object
* @param {Object} [Astro.params] - Routing parameters
* @param {string} [Astro.params.locale] - The current locale parameter
* @returns {Locale} The determined locale
*/
export const getLocale = (Astro: { params?: { locale?: string } }) => {
if (Astro.params?.locale) {
return Astro.params.locale as Locale
}
return CONSTANT.I18N.DEFAULT_LOCALE as Locale
}
/**
* List of all supported locales
* @type {Locale[]}
*/
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value)
const otherLocales = CONSTANT.I18N.LOCALES.filter(
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
)
/**
* List of locales excluding the default locale
* @type {Locale[]}
*/
const otherLocales = CONSTANT.I18N.LOCALES.filter(({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE)
/**
* Retrieves locales other than the default locale
* @returns {Locale[]} Array of non-default locales
*/
export const getOtherLocales = () => otherLocales
/**
* Type definition for UI translations based on the English translation
* @typedef {Object} UI
*/
export type UI = typeof UI_EN
/**
* Mapping of locales to their UI translation objects
* @type {Object.<Locale, UI>}
*/
export const ui = { en: UI_EN }
/**
* Retrieves UI translations for a given locale, merging with default translations
* @param {Locale} [locale] - The target locale for translations
* @returns {UI} Merged UI translations
*/
export const getUI = (locale?: Locale | string): UI => {
const validLocale = locales.includes(locale as Locale)
? locale
: CONSTANT.I18N.DEFAULT_LOCALE
const validLocale = locales.includes(locale as Locale) ? locale : CONSTANT.I18N.DEFAULT_LOCALE
const defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE]
const localeUI = ui[validLocale as Locale]
function deepMerge<T>(defaultObj: T, overrideObj: Partial<T>): T {
if (typeof defaultObj !== 'object' || defaultObj === null)
/**
* Recursively merges two objects, with the override object taking precedence
* @template T
* @param {T} defaultObj - The default object to merge from
* @param {Partial<T>} overrideObj - The object to merge over the default
* @returns {T} The deeply merged object
*/
function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T {
// Handle non-object cases
if (typeof defaultObj !== 'object' || defaultObj === null) {
return (overrideObj ?? defaultObj) as T
if (typeof overrideObj !== 'object' || overrideObj === null)
}
if (typeof overrideObj !== 'object' || overrideObj === null) {
return (overrideObj ?? defaultObj) as T
const result: any = Array.isArray(defaultObj)
? [...defaultObj]
: { ...defaultObj }
for (const key in defaultObj) {
if (Object.prototype.hasOwnProperty.call(defaultObj, key)) {
result[key] = deepMerge(
(defaultObj as any)[key],
(overrideObj as any)?.[key],
)
}
// Create a new object or array based on the default object's type
const result = Array.isArray(defaultObj) ? [...defaultObj] : { ...defaultObj }
// Merge properties from the default object
for (const key of Object.keys(defaultObj) as Array<keyof T>) {
const defaultValue = defaultObj[key]
const overrideValue = overrideObj[key]
// Recursively merge nested objects
if (
defaultValue !== null &&
overrideValue !== null &&
typeof defaultValue === 'object' &&
typeof overrideValue === 'object'
) {
// Type assertion to handle nested merging
;(result as Record<keyof T, unknown>)[key] = deepMerge(defaultValue as object, overrideValue as Partial<object>)
} else if (overrideValue !== undefined) {
// Override with the new value if it exists
;(result as Record<keyof T, unknown>)[key] = overrideValue
}
}
for (const key in overrideObj) {
// Add any new properties from overrideObj
for (const key of Object.keys(overrideObj) as Array<keyof T>) {
if (!(key in defaultObj)) {
result[key] = (overrideObj as any)[key]
;(result as Record<keyof T, unknown>)[key] = overrideObj[key]
}
}
return result as T
}
return deepMerge<UI>(defaultUI, localeUI)
return deepMerge(defaultUI, localeUI)
}
/**
* Generates static paths for internationalization
* @type {GetStaticPaths}
* @returns {Array} An array of static paths for different locales
*/
export const getStaticPaths = (() => {
return [
{
params: { locale: undefined },
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
},
...CONSTANT.I18N.LOCALES.filter(
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
).map(({ value }) => ({
...CONSTANT.I18N.LOCALES.filter(({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE).map(({ value }) => ({
params: { locale: value },
props: {
locale: value,
@ -80,6 +146,10 @@ export const getStaticPaths = (() => {
]
}) satisfies GetStaticPaths
/**
* Retrieves all available locales, including both default and non-default
* @returns {Locale[]} Combined array of all locales
*/
export const getLocales = () => {
return [...locales, ...otherLocales]
}