chore(biome): update biome rules

This commit is contained in:
Shintaro Jokagi 2025-05-15 14:19:39 +12:00
parent 78627c7749
commit 017cb2a7f5
No known key found for this signature in database
GPG key ID: 0DDF8FA44C9A0DA8
45 changed files with 589 additions and 596 deletions

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'],
}

View file

@ -21,5 +21,11 @@
".git", ".git",
"dist" "dist"
] ]
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
} }
} }

View file

@ -1,19 +1,19 @@
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) { export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
return { return {
initial: { opacity: 0.001, translateY: 20, filter: "blur(4px)" }, initial: { opacity: 0.001, translateY: 20, filter: 'blur(4px)' },
whileInView: { whileInView: {
opacity: 1, opacity: 1,
translateY: 0, translateY: 0,
filter: "blur(0px)", filter: 'blur(0px)',
transition: { duration, delay }, transition: { duration, delay },
}, },
viewport: { once: once }, viewport: { once: once },
}; }
} }
export function getZoomInAnimation(delay = 0) { export function getZoomInAnimation(delay = 0) {
return { return {
initial: { scale: 0.8, opacity: 0.001 }, initial: { scale: 0.8, opacity: 0.001 },
whileInView: { scale: 1, opacity: 1, transition: { duration: 0.2, delay } }, whileInView: { scale: 1, opacity: 1, transition: { duration: 0.2, delay } },
}; }
} }

View file

@ -1,14 +1,14 @@
--- ---
import { ArrowLeft } from "lucide-astro"; import { ArrowLeft } from 'lucide-astro'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { routes: {
mods: { slug }, mods: { slug },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<button <button

View file

@ -1,9 +1,9 @@
--- ---
import { getLocale, getPath } from "~/utils/i18n"; import { getLocale, getPath } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); 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

@ -1,7 +1,7 @@
--- ---
const { white, multiplier = 0.9, class: classList } = Astro.props; const { white, multiplier = 0.9, class: classList } = Astro.props
const sizes = [216, 396, 576, 756]; const sizes = [216, 396, 576, 756]
const borderWidths = [20, 30, 40, 50]; const borderWidths = [20, 30, 40, 50]
--- ---
<div <div

View file

@ -1,20 +1,20 @@
--- ---
import Image from "astro/components/Image.astro"; import Image from 'astro/components/Image.astro'
import { Check, Github } from "lucide-astro"; import { Check, Github } from 'lucide-astro'
import { motion } from "motion/react"; import { motion } from 'motion/react'
import { getTitleAnimation } from "~/animations"; import { getTitleAnimation } from '~/animations'
import ComImage from "~/assets/ComImage.png"; import ComImage from '~/assets/ComImage.png'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { routes: {
index: { community }, index: { community },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<section <section

View file

@ -1,31 +1,27 @@
--- ---
import { motion } from "motion/react"; import { motion } from 'motion/react'
import { getTitleAnimation } from "~/animations"; import { getTitleAnimation } from '~/animations'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import CompactModeVideo from "~/assets/CompactMode.webm"; import CompactModeVideo from '~/assets/CompactMode.webm'
import GlanceVideo from "~/assets/Glance.webm"; import GlanceVideo from '~/assets/Glance.webm'
import SplitViewsVideo from "~/assets/SplitViews.webm"; import SplitViewsVideo from '~/assets/SplitViews.webm'
import WorkspacesVideo from "~/assets/Workspaces.webm"; import WorkspacesVideo from '~/assets/Workspaces.webm'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
import Video from "./Video.astro"; import Video from './Video.astro'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { routes: {
index: { features }, index: { features },
}, },
} = getUI(locale); } = getUI(locale)
const { const { title1 = features.title1, title2 = features.title2, title3 = features.title3 } = Astro.props
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 <section

View file

@ -1,16 +1,16 @@
--- ---
import { ArrowRight } from "lucide-astro"; import { ArrowRight } from 'lucide-astro'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Circles from "~/components/Circles.astro"; import Circles from '~/components/Circles.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"; import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
import { getLocale, getPath, getUI } from "~/utils/i18n"; import { getLocale, getPath, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); const getLocalePath = getPath(locale)
const { const {
components: { footer }, components: { footer },
} = getUI(locale); } = getUI(locale)
--- ---
<footer <footer

View file

@ -1,34 +1,34 @@
--- ---
import { ArrowRight } from "lucide-astro"; import { ArrowRight } from 'lucide-astro'
import { motion } from "motion/react"; import { motion } from 'motion/react'
import { getTitleAnimation } from "~/animations"; import { getTitleAnimation } from '~/animations'
import HomePageVideo from "~/assets/HomePageVideo.webm"; import HomePageVideo from '~/assets/HomePageVideo.webm'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import Title from "~/components/Title.astro"; import Title from '~/components/Title.astro'
import { getLocale, getPath, getUI } from "~/utils/i18n"; import { getLocale, getPath, getUI } from '~/utils/i18n'
import SocialMediaStrip from "./SocialMediaStrip.astro"; import SocialMediaStrip from './SocialMediaStrip.astro'
import Video from "./Video.astro"; import Video from './Video.astro'
let titleAnimationCounter = 0; let titleAnimationCounter = 0
function getNewAnimationDelay() { function getNewAnimationDelay() {
titleAnimationCounter++; titleAnimationCounter++
return titleAnimationCounter * 0.15; return titleAnimationCounter * 0.15
} }
function getHeroTitleAnimation() { function getHeroTitleAnimation() {
return getTitleAnimation(getNewAnimationDelay()); return getTitleAnimation(getNewAnimationDelay())
} }
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); const getLocalePath = getPath(locale)
const { const {
routes: { routes: {
index: { hero }, index: { hero },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<header <header

View file

@ -1,5 +1,5 @@
--- ---
const { class: className } = Astro.props; const { class: className } = Astro.props
--- ---
<svg <svg

View file

@ -1,13 +1,13 @@
--- ---
import { getLocale, getPath, getUI } from "~/utils/i18n"; import { getLocale, getPath, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); const getLocalePath = getPath(locale)
const { const {
components: { components: {
nav: { menu }, nav: { menu },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<!-- Hidden checkbox for menu toggle --> <!-- Hidden checkbox for menu toggle -->

View file

@ -1,21 +1,21 @@
import { icon, library } from "@fortawesome/fontawesome-svg-core"; import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { faSort, faSortDown, faSortUp } from "@fortawesome/free-solid-svg-icons"; import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from 'preact/hooks'
import { useModsSearch } from "~/hooks/useModsSearch"; import { useModsSearch } from '~/hooks/useModsSearch'
import type { ZenTheme } from "~/mods"; import type { ZenTheme } from '~/mods'
import { type Locale, getUI } from "~/utils/i18n"; import { type Locale, getUI } from '~/utils/i18n'
// Add icons to the library // Add icons to the library
library.add(faSort, faSortUp, faSortDown); library.add(faSort, faSortUp, faSortDown)
// Create icon objects // Create icon objects
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" }); const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" }); const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" }); const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
interface ModsListProps { interface ModsListProps {
allMods: ZenTheme[]; allMods: ZenTheme[]
locale: Locale; locale: Locale
} }
export default function ModsList({ allMods, locale }: ModsListProps) { export default function ModsList({ allMods, locale }: ModsListProps) {
@ -34,71 +34,71 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
setLimit, setLimit,
mods: paginatedMods, mods: paginatedMods,
// searchParams, // searchParams,
} = useModsSearch(allMods); } = useModsSearch(allMods)
const [pageInput, setPageInput] = useState(page.toString()); const [pageInput, setPageInput] = useState(page.toString())
// Keep page input in sync with actual page // Keep page input in sync with actual page
useEffect(() => { useEffect(() => {
setPageInput(page.toString()); setPageInput(page.toString())
}, [page]); }, [page])
function getSortIcon(state: "default" | "asc" | "desc") { function getSortIcon(state: 'default' | 'asc' | 'desc') {
if (state === "asc") return ascSortIcon; if (state === 'asc') return ascSortIcon
if (state === "desc") return descSortIcon; if (state === 'desc') return descSortIcon
return defaultSortIcon; return defaultSortIcon
} }
function handleSearch(e: Event) { function handleSearch(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement
setSearch(target.value); setSearch(target.value)
} }
function handleLimitChange(e: Event) { function handleLimitChange(e: Event) {
const target = e.target as HTMLSelectElement; const target = e.target as HTMLSelectElement
setLimit(Number.parseInt(target.value, 10)); setLimit(Number.parseInt(target.value, 10))
} }
function handlePageSubmit(e: Event) { function handlePageSubmit(e: Event) {
e.preventDefault(); e.preventDefault()
const newPage = Number.parseInt(pageInput, 10); const newPage = Number.parseInt(pageInput, 10)
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) { if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
setPage(newPage); setPage(newPage)
window.scrollTo(0, 0); window.scrollTo(0, 0)
} else { } else {
setPageInput(page.toString()); setPageInput(page.toString())
} }
} }
function handlePageInputChange(e: Event) { function handlePageInputChange(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement
setPageInput(target.value); setPageInput(target.value)
} }
function navigatePage(pageNum: number) { function navigatePage(pageNum: number) {
setPage(pageNum); setPage(pageNum)
window.scrollTo(0, 0); window.scrollTo(0, 0)
} }
const { const {
routes: { mods }, routes: { mods },
} = getUI(locale); } = getUI(locale)
function renderPagination() { function renderPagination() {
if (totalPages <= 1) return null; if (totalPages <= 1) return null
return ( return (
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8"> <div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
<button <button
type="button" type="button"
onClick={() => navigatePage(page - 1)} onClick={() => navigatePage(page - 1)}
className={`px-3 py-2 ${ className={`px-3 py-2 ${
page === 1 ? "pointer-events-none text-gray-400" : "text-dark hover:text-gray-600" page === 1 ? 'pointer-events-none text-gray-400' : 'text-dark hover:text-gray-600'
}`} }`}
> >
&lt; &lt;
</button> </button>
<form onSubmit={handlePageSubmit} className="flex items-center gap-2"> <form onSubmit={handlePageSubmit} className="flex items-center gap-2">
{mods.pagination.pagination.split("{input}").map((value, index) => { {mods.pagination.pagination.split('{input}').map((value, index) => {
if (index === 0) { if (index === 0) {
return ( return (
<input <input
@ -108,15 +108,15 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm" className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
aria-label="Page number" aria-label="Page number"
/> />
); )
} }
return ( return (
<span key={value} className="text-sm"> <span key={value} className="text-sm">
{value {value
.replace("{totalPages}", totalPages.toString()) .replace('{totalPages}', totalPages.toString())
.replace("{totalItems}", totalItems.toString())} .replace('{totalItems}', totalItems.toString())}
</span> </span>
); )
})} })}
</form> </form>
<button <button
@ -124,14 +124,14 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
onClick={() => navigatePage(page + 1)} onClick={() => navigatePage(page + 1)}
className={`px-3 py-2 ${ className={`px-3 py-2 ${
page === totalPages page === totalPages
? "pointer-events-none text-gray-400" ? 'pointer-events-none text-gray-400'
: "text-dark hover:text-gray-600" : 'text-dark hover:text-gray-600'
}`} }`}
> >
&gt; &gt;
</button> </button>
</div> </div>
); )
} }
return ( return (
@ -234,5 +234,5 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
{renderPagination()} {renderPagination()}
</div> </div>
); )
} }

View file

@ -1,21 +1,21 @@
--- ---
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar"; import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
import { ArrowRight, ChevronDown, Download, Menu } from "lucide-astro"; import { ArrowRight, ChevronDown, Download, Menu } from 'lucide-astro'
import { motion } from "motion/react"; import { motion } from 'motion/react'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import { getLocale, getPath, getUI } from "~/utils/i18n"; import { getLocale, getPath, getUI } from '~/utils/i18n'
import { getTitleAnimation } from "../animations.ts"; import { getTitleAnimation } from '../animations.ts'
import Logo from "./Logo.astro"; import Logo from './Logo.astro'
import MobileMenu from "./MobileMenu.astro"; import MobileMenu from './MobileMenu.astro'
import ThemeSwitch from "./ThemeSwitch.astro"; import ThemeSwitch from './ThemeSwitch.astro'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); const getLocalePath = getPath(locale)
const { const {
components: { components: {
nav: { brand, menu }, nav: { brand, menu },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<!-- Desktop Navigation --> <!-- Desktop Navigation -->

View file

@ -1,41 +1,41 @@
--- ---
import { Accordion, AccordionItem } from "free-astro-components"; import { Accordion, AccordionItem } from 'free-astro-components'
import { Info } from "lucide-astro"; import { Info } from 'lucide-astro'
import { releaseNotes as releaseNotesData } from "~/release-notes"; import { releaseNotes as releaseNotesData } from '~/release-notes'
import { getLocale, getPath, getUI } from "~/utils/i18n"; import { getLocale, getPath, getUI } from '~/utils/i18n'
import { import {
type BreakingChange, type BreakingChange,
type ReleaseNote, type ReleaseNote,
getReleaseNoteFirefoxVersion, getReleaseNoteFirefoxVersion,
} from "../release-notes"; } from '../release-notes'
export type Props = ReleaseNote; export type Props = ReleaseNote
const { isTwilight, ...props } = Astro.props; const { isTwilight, ...props } = Astro.props
const locale = getLocale(Astro); const locale = getLocale(Astro)
const getLocalePath = getPath(locale); const getLocalePath = getPath(locale)
const { const {
routes: { routes: {
releaseNotes: { releaseNotes: {
components: { releaseNoteItem }, components: { releaseNoteItem },
}, },
}, },
} = getUI(locale); } = getUI(locale)
let date: Date | undefined; let date: Date | undefined
if (props.date) { if (props.date) {
const [day, month, year] = props.date.split("/"); const [day, month, year] = props.date.split('/')
date = new Date(Date.parse(`${year}-${month}-${day}`)); date = new Date(Date.parse(`${year}-${month}-${day}`))
} }
const ffVersion = getReleaseNoteFirefoxVersion(props); const ffVersion = getReleaseNoteFirefoxVersion(props)
const currentReleaseIndex = releaseNotesData.findIndex( const currentReleaseIndex = releaseNotesData.findIndex(
(releaseNote: ReleaseNote) => releaseNote.version === props.version, (releaseNote: ReleaseNote) => releaseNote.version === props.version,
); )
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]; const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
let compareLink = ""; let compareLink = ''
if (prevReleaseNote && !isTwilight) { if (prevReleaseNote && !isTwilight) {
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`; compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`
} }
--- ---

View file

@ -1,21 +1,21 @@
--- ---
const { gap = 4 } = Astro.props; const { gap = 4 } = Astro.props
import { icon, library } from "@fortawesome/fontawesome-svg-core"; import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { import {
faBluesky, faBluesky,
faGithub, faGithub,
faMastodon, faMastodon,
faReddit, faReddit,
faXTwitter, faXTwitter,
} from "@fortawesome/free-brands-svg-icons"; } from '@fortawesome/free-brands-svg-icons'
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit); library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
const Mastodon = icon({ prefix: "fab", iconName: "mastodon" }); const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" }); const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
const Github = icon({ prefix: "fab", iconName: "github" }); const Github = icon({ prefix: 'fab', iconName: 'github' })
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" }); const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
const Reddit = icon({ prefix: "fab", iconName: "reddit" }); const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
--- ---
<ul class={`flex items-center opacity-80 gap-${gap}`}> <ul class={`flex items-center opacity-80 gap-${gap}`}>

View file

@ -1,21 +1,21 @@
--- ---
import { motion } from "motion/react"; import { motion } from 'motion/react'
import { getTitleAnimation } from "~/animations"; import { getTitleAnimation } from '~/animations'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
import tutaLogo from "~/assets/tuta-logo.png"; import tutaLogo from '~/assets/tuta-logo.png'
import Image from "astro/components/Image.astro"; import Image from 'astro/components/Image.astro'
const { showSponsors = true } = Astro.props; const { showSponsors = true } = Astro.props
const { const {
routes: { routes: {
index: { sponsors }, index: { sponsors },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}> <section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>

View file

@ -1,10 +1,10 @@
--- ---
interface Props { interface Props {
label?: string; label?: string
className?: string; className?: string
} }
const { label, className = "" } = Astro.props; const { label, className = '' } = Astro.props
--- ---
<button <button

View file

@ -1,5 +1,5 @@
--- ---
const { class: className } = Astro.props; const { class: className } = Astro.props
--- ---
<h1 class:list={['title text-dark', className]}> <h1 class:list={['title text-dark', className]}>

View file

@ -1,6 +1,6 @@
--- ---
const { src, class: className, ...rest } = Astro.props; const { src, class: className, ...rest } = Astro.props
const type = src.split(".").pop() || "webm"; const type = src.split('.').pop() || 'webm'
--- ---
<video <video

View file

@ -1,12 +1,12 @@
--- ---
interface Props { interface Props {
label: string; label: string
href: string; href: string
variant?: string; variant?: string
checksum?: string; checksum?: string
} }
const { label, href, checksum } = Astro.props; const { label, href, checksum } = Astro.props
--- ---
<div class="relative flex flex-col"> <div class="relative flex flex-col">

View file

@ -1,32 +1,32 @@
--- ---
interface ReleaseInfo { interface ReleaseInfo {
label?: string; label?: string
link: string; link: string
checksum?: string; checksum?: string
} }
interface PlatformReleases { interface PlatformReleases {
universal?: ReleaseInfo; universal?: ReleaseInfo
all?: ReleaseInfo; all?: ReleaseInfo
tarball?: ReleaseInfo; tarball?: ReleaseInfo
x86_64?: { tarball: ReleaseInfo } | ReleaseInfo; x86_64?: { tarball: ReleaseInfo } | ReleaseInfo
arm64?: ReleaseInfo; arm64?: ReleaseInfo
flathub?: { all: ReleaseInfo }; flathub?: { all: ReleaseInfo }
} }
interface Props { interface Props {
platform: "mac" | "windows" | "linux"; platform: 'mac' | 'windows' | 'linux'
icon: string[]; icon: string[]
title: string; title: string
description: string; description: string
releases: PlatformReleases; releases: PlatformReleases
} }
const { platform, icon, title, description, releases } = Astro.props; const { platform, icon, title, description, releases } = Astro.props
import { Image } from "astro:assets"; import { Image } from 'astro:assets'
import AppIconDark from "../../assets/app-icon-dark.png"; import AppIconDark from '../../assets/app-icon-dark.png'
import AppIconLight from "../../assets/app-icon-light.png"; import AppIconLight from '../../assets/app-icon-light.png'
import DownloadCard from "./ButtonCard.astro"; import DownloadCard from './ButtonCard.astro'
--- ---
<div <div

View file

@ -7,55 +7,55 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
return { return {
macos: { macos: {
universal: { universal: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
label: "Universal", label: 'Universal',
checksum: checksums["zen.macos-universal.dmg"], checksum: checksums['zen.macos-universal.dmg'],
}, },
}, },
windows: { windows: {
x86_64: { x86_64: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
label: "64-bit (Recommended)", label: '64-bit (Recommended)',
checksum: checksums["zen.installer.exe"], checksum: checksums['zen.installer.exe'],
}, },
arm64: { arm64: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
label: "ARM64", label: 'ARM64',
checksum: checksums["zen.installer-arm64.exe"], checksum: checksums['zen.installer-arm64.exe'],
}, },
}, },
linux: { linux: {
x86_64: { x86_64: {
tarball: { tarball: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
label: "Tarball x86_64", label: 'Tarball x86_64',
checksum: checksums["zen.linux-x86_64.tar.xz"], checksum: checksums['zen.linux-x86_64.tar.xz'],
}, },
appImage: { appImage: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage',
label: "AppImage x86_64", label: 'AppImage x86_64',
checksum: checksums["zen-x86_64.AppImage"], checksum: checksums['zen-x86_64.AppImage'],
}, },
}, },
aarch64: { aarch64: {
tarball: { tarball: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
label: "Tarball aarch64", label: 'Tarball aarch64',
checksum: checksums["zen.linux-aarch64.tar.xz"], checksum: checksums['zen.linux-aarch64.tar.xz'],
}, },
appImage: { appImage: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage", link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
label: "AppImage aarch64", label: 'AppImage aarch64',
checksum: checksums["zen-aarch64.AppImage"], checksum: checksums['zen-aarch64.AppImage'],
}, },
}, },
flathub: { flathub: {
all: { all: {
link: "https://flathub.org/apps/app.zen_browser.zen", link: 'https://flathub.org/apps/app.zen_browser.zen',
label: "Flathub", label: 'Flathub',
}, },
}, },
}, },
}; }
} }
--- ---

View file

@ -1,4 +1,4 @@
export const I18N = { export const I18N = {
DEFAULT_LOCALE: "en", DEFAULT_LOCALE: 'en',
LOCALES: [{ label: "English", value: "en" }], LOCALES: [{ label: 'English', value: 'en' }],
} as const; } as const

View file

@ -1,5 +1,5 @@
import { I18N } from "./i18n"; import { I18N } from './i18n'
export const CONSTANT = { export const CONSTANT = {
I18N, I18N,
}; }

View file

@ -1,91 +1,91 @@
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from 'preact/hooks'
import type { ZenTheme } from "../mods"; import type { ZenTheme } from '../mods'
type SortOrder = "default" | "asc" | "desc"; type SortOrder = 'default' | 'asc' | 'desc'
interface ModsSearchState { interface ModsSearchState {
search: string; search: string
createdSort: SortOrder; createdSort: SortOrder
updatedSort: SortOrder; updatedSort: SortOrder
page: number; page: number
limit: number; limit: number
} }
const DEFAULT_LIMIT = 12; const DEFAULT_LIMIT = 12
export function useModsSearch(mods: ZenTheme[]) { export function useModsSearch(mods: ZenTheme[]) {
const [searchParams, setSearchParams] = useState<URLSearchParams>(); const [searchParams, setSearchParams] = useState<URLSearchParams>()
const [state, setState] = useState<ModsSearchState>({ const [state, setState] = useState<ModsSearchState>({
search: "", search: '',
createdSort: "desc", createdSort: 'desc',
updatedSort: "default", updatedSort: 'default',
page: 1, page: 1,
limit: DEFAULT_LIMIT, limit: DEFAULT_LIMIT,
}); })
// Initialize search params // Initialize search params
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
setSearchParams(params); setSearchParams(params)
setState({ setState({
search: params.get("q") || "", search: params.get('q') || '',
createdSort: (params.get("created") as SortOrder) || "desc", createdSort: (params.get('created') as SortOrder) || 'desc',
updatedSort: (params.get("updated") as SortOrder) || "default", updatedSort: (params.get('updated') as SortOrder) || 'default',
page: Number.parseInt(params.get("page") || "1", 10), page: Number.parseInt(params.get('page') || '1', 10),
limit: Number.parseInt(params.get("limit") || String(DEFAULT_LIMIT), 10), limit: Number.parseInt(params.get('limit') || String(DEFAULT_LIMIT), 10),
}); })
}, []); }, [])
// Update URL when state changes // Update URL when state changes
useEffect(() => { useEffect(() => {
if (!searchParams) return; if (!searchParams) return
if (state.search) { if (state.search) {
searchParams.set("q", state.search); searchParams.set('q', state.search)
} else { } else {
searchParams.delete("q"); searchParams.delete('q')
} }
if (state.createdSort !== "default") { if (state.createdSort !== 'default') {
searchParams.set("created", state.createdSort); searchParams.set('created', state.createdSort)
} else { } else {
searchParams.delete("created"); searchParams.delete('created')
} }
if (state.updatedSort !== "default") { if (state.updatedSort !== 'default') {
searchParams.set("updated", state.updatedSort); searchParams.set('updated', state.updatedSort)
} else { } else {
searchParams.delete("updated"); searchParams.delete('updated')
} }
if (state.page > 1) { if (state.page > 1) {
searchParams.set("page", state.page.toString()); searchParams.set('page', state.page.toString())
} else { } else {
searchParams.delete("page"); searchParams.delete('page')
} }
if (state.limit !== DEFAULT_LIMIT) { if (state.limit !== DEFAULT_LIMIT) {
searchParams.set("limit", state.limit.toString()); searchParams.set('limit', state.limit.toString())
} else { } else {
searchParams.delete("limit"); searchParams.delete('limit')
} }
const newUrl = `${window.location.pathname}${ const newUrl = `${window.location.pathname}${
searchParams.toString() ? `?${searchParams.toString()}` : "" searchParams.toString() ? `?${searchParams.toString()}` : ''
}`; }`
if (state.page > 1) { if (state.page > 1) {
window.history.pushState({}, "", newUrl); window.history.pushState({}, '', newUrl)
} else { } else {
window.history.replaceState({}, "", newUrl); window.history.replaceState({}, '', newUrl)
} }
}, [state, searchParams]); }, [state, searchParams])
const filteredMods = (() => { const filteredMods = (() => {
let filtered = [...mods]; let filtered = [...mods]
// Filter by search // Filter by search
const searchTerm = state.search.toLowerCase(); const searchTerm = state.search.toLowerCase()
if (searchTerm) { if (searchTerm) {
filtered = filtered.filter( filtered = filtered.filter(
(mod) => (mod) =>
@ -93,66 +93,66 @@ export function useModsSearch(mods: ZenTheme[]) {
mod.description.toLowerCase().includes(searchTerm) || mod.description.toLowerCase().includes(searchTerm) ||
mod.author.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 // Sort by createdAt if chosen
if (state.createdSort !== "default") { if (state.createdSort !== 'default') {
filtered.sort((a, b) => { 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; return state.createdSort === 'asc' ? diff : -diff
}); })
} }
// Sort by updatedAt if chosen // Sort by updatedAt if chosen
if (state.updatedSort !== "default") { if (state.updatedSort !== 'default') {
filtered.sort((a, b) => { 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; return state.updatedSort === 'asc' ? diff : -diff
}); })
} }
return filtered; return filtered
})(); })()
// Calculate pagination // Calculate pagination
const totalPages = Math.ceil(filteredMods.length / state.limit); const totalPages = Math.ceil(filteredMods.length / state.limit)
const startIndex = (state.page - 1) * state.limit; const startIndex = (state.page - 1) * state.limit
const endIndex = startIndex + state.limit; const endIndex = startIndex + state.limit
const paginatedMods = filteredMods.slice(startIndex, endIndex); const paginatedMods = filteredMods.slice(startIndex, endIndex)
const setSearch = (search: string) => { const setSearch = (search: string) => {
setState((prev) => ({ ...prev, search, page: 1 })); // Reset page when search changes setState((prev) => ({ ...prev, search, page: 1 })) // Reset page when search changes
}; }
const toggleCreatedSort = () => { const toggleCreatedSort = () => {
setState((prev) => ({ setState((prev) => ({
...prev, ...prev,
createdSort: createdSort:
prev.createdSort === "default" ? "asc" : prev.createdSort === "asc" ? "desc" : "default", prev.createdSort === 'default' ? 'asc' : prev.createdSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes page: 1, // Reset page when sort changes
})); }))
}; }
const toggleUpdatedSort = () => { const toggleUpdatedSort = () => {
setState((prev) => ({ setState((prev) => ({
...prev, ...prev,
updatedSort: updatedSort:
prev.updatedSort === "default" ? "asc" : prev.updatedSort === "asc" ? "desc" : "default", prev.updatedSort === 'default' ? 'asc' : prev.updatedSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes page: 1, // Reset page when sort changes
})); }))
}; }
const setPage = (page: number) => { const setPage = (page: number) => {
setState((prev) => ({ setState((prev) => ({
...prev, ...prev,
page: Math.max(1, Math.min(page, totalPages)), page: Math.max(1, Math.min(page, totalPages)),
})); }))
}; }
const setLimit = (limit: number) => { const setLimit = (limit: number) => {
setState((prev) => ({ ...prev, limit, page: 1 })); // Reset page when limit changes setState((prev) => ({ ...prev, limit, page: 1 })) // Reset page when limit changes
}; }
return { return {
search: state.search, search: state.search,
@ -169,5 +169,5 @@ export function useModsSearch(mods: ZenTheme[]) {
setLimit, setLimit,
mods: paginatedMods, mods: paginatedMods,
searchParams, searchParams,
}; }
} }

View file

@ -1,23 +1,23 @@
--- ---
interface Props { interface Props {
title: string; title: string
description?: string; description?: string
ogImage?: string; ogImage?: string
isHome?: boolean; isHome?: boolean
redirect?: string; redirect?: string
} }
const { title, description, ogImage, isHome, redirect } = Astro.props; const { title, description, ogImage, isHome, redirect } = Astro.props
const defaultDescription = 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."; '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"; const defaultOgImage = '/share-pic.png'
import "@fontsource/bricolage-grotesque/400.css"; import '@fontsource/bricolage-grotesque/400.css'
import "@fontsource/bricolage-grotesque/500.css"; import '@fontsource/bricolage-grotesque/500.css'
import "@fontsource/bricolage-grotesque/600.css"; import '@fontsource/bricolage-grotesque/600.css'
import Footer from "~/components/Footer.astro"; import Footer from '~/components/Footer.astro'
import NavBar from "~/components/NavBar.astro"; import NavBar from '~/components/NavBar.astro'
import { getLocale } from "~/utils/i18n"; import { getLocale } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
--- ---
<script is:inline data-cfasync="false"> <script is:inline data-cfasync="false">

View file

@ -1,41 +1,41 @@
import { format } from "date-fns"; import { format } from 'date-fns'
export interface ZenTheme { export interface ZenTheme {
name: string; name: string
description: string; description: string
image: string; image: string
downloadUrl: string; downloadUrl: string
id: string; id: string
homepage?: string; homepage?: string
readme: string; readme: string
preferences?: string; preferences?: string
isColorTheme: boolean; isColorTheme: boolean
author: string; author: string
version: string; version: string
tags: string[]; tags: string[]
createdAt: Date; createdAt: Date
updatedAt: Date; updatedAt: Date
} }
const THEME_API = "https://zen-browser.github.io/theme-store/themes.json"; const THEME_API = 'https://zen-browser.github.io/theme-store/themes.json'
export async function getAllMods(): Promise<ZenTheme[]> { export async function getAllMods(): Promise<ZenTheme[]> {
try { try {
const res = await fetch(THEME_API); const res = await fetch(THEME_API)
const json = await res.json(); const json = await res.json()
// convert dict to array // convert dict to array
const mods = Object.keys(json).map((key) => json[key]); const mods = Object.keys(json).map((key) => json[key])
return mods; return mods
} catch (error) { } catch (error) {
console.error(error); console.error(error)
return []; return []
} }
} }
export function getAuthorLink(author: string): string { export function getAuthorLink(author: string): string {
return `https://github.com/${author}`; return `https://github.com/${author}`
} }
export function getLocalizedDate(date: Date): string { export function getLocalizedDate(date: Date): string {
return format(date, "PP"); return format(date, 'PP')
} }

View file

@ -1,5 +1,5 @@
--- ---
import NotFound from "./[...locale]/404.astro"; import NotFound from './[...locale]/404.astro'
--- ---
<NotFound /> <NotFound />

View file

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

View file

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

View file

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

View file

@ -1,35 +1,35 @@
--- ---
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import DownloadScript from "~/components/download/DownloadScript.astro"; import DownloadScript from '~/components/download/DownloadScript.astro'
import PlatformDownload from "~/components/download/PlatformDownload.astro"; import PlatformDownload from '~/components/download/PlatformDownload.astro'
import { getReleasesWithChecksums } from "~/components/download/release-data.astro"; import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { getChecksums } from "~/utils/githubChecksums"; import { getChecksums } from '~/utils/githubChecksums'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
import { icon, library } from "@fortawesome/fontawesome-svg-core"; 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"; import { ExternalLink, Lock } from 'lucide-astro'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { download }, routes: { download },
layout, layout,
} = getUI(locale); } = getUI(locale)
library.add(faWindows, faLinux, faApple, faGithub); library.add(faWindows, faLinux, faApple, faGithub)
const windowsIcon = icon({ prefix: "fab", iconName: "windows" }); const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
const linuxIcon = icon({ prefix: "fab", iconName: "linux" }); const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
const appleIcon = icon({ prefix: "fab", iconName: "apple" }); const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
const githubIcon = icon({ prefix: "fab", iconName: "github" }); const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
const checksums = await getChecksums(); const checksums = await getChecksums()
const releases = getReleasesWithChecksums(checksums); const releases = getReleasesWithChecksums(checksums)
const platformNames = download.platformNames; const platformNames = download.platformNames
const platformDescriptions = download.platformDescriptions; const platformDescriptions = download.platformDescriptions
--- ---
<DownloadScript /> <DownloadScript />

View file

@ -1,10 +1,10 @@
import rss, { type RSSOptions } from "@astrojs/rss"; import rss, { type RSSOptions } from '@astrojs/rss'
import { releaseNotes } from "~/release-notes"; import { releaseNotes } from '~/release-notes'
import type { ReleaseNote } from "~/release-notes"; import type { ReleaseNote } from '~/release-notes'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
/** The default number of entries to include in the RSS feed. */ /** The default number of entries to include in the RSS feed. */
const RSS_ENTRY_LIMIT = 20; const RSS_ENTRY_LIMIT = 20
/** /**
* Handles the GET request for the `feed.xml` endpoint. * Handles the GET request for the `feed.xml` endpoint.
@ -13,11 +13,11 @@ const RSS_ENTRY_LIMIT = 20;
export function GET(context: { url: URL }) { export function GET(context: { url: URL }) {
// Just in case the release notes array is empty for whatever reason. // Just in case the release notes array is empty for whatever reason.
const latestDate = const latestDate =
releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date(); releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date()
const rssData: RSSOptions = { const rssData: RSSOptions = {
title: "Zen Browser Release Notes", title: 'Zen Browser Release Notes',
description: "Release Notes for the Zen Browser", description: 'Release Notes for the Zen Browser',
site: context.url, site: context.url,
items: [], items: [],
customData: ` customData: `
@ -31,7 +31,7 @@ export function GET(context: { url: URL }) {
<link>https://www.zen-browser.app</link> <link>https://www.zen-browser.app</link>
</image> </image>
`, `,
}; }
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) { for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
rssData.items.push({ rssData.items.push({
@ -40,10 +40,10 @@ export function GET(context: { url: URL }) {
pubDate: formatRssDate(releaseNote.date as string), pubDate: formatRssDate(releaseNote.date as string),
description: releaseNote.extra, description: releaseNote.extra,
content: formatReleaseNote(releaseNote), content: formatReleaseNote(releaseNote),
}); })
} }
return rss(rssData); return rss(rssData)
} }
/** /**
@ -54,15 +54,15 @@ export function GET(context: { url: URL }) {
* @returns The passed in date string as a Date object. * @returns The passed in date string as a Date object.
*/ */
function formatRssDate(dateStr: string) { function formatRssDate(dateStr: string) {
const splitDate = dateStr.split("/"); const splitDate = dateStr.split('/')
if (splitDate.length !== 3) { if (splitDate.length !== 3) {
throw new Error("Invalid date format"); throw new Error('Invalid date format')
} }
const day = Number(splitDate[0]); const day = Number(splitDate[0])
const month = Number(splitDate[1]) - 1; const month = Number(splitDate[1]) - 1
const year = Number(splitDate[2]); const year = Number(splitDate[2])
return new Date(year, month, day); return new Date(year, month, day)
} }
/** /**
@ -74,83 +74,83 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
let content = `<p> let content = `<p>
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>. If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>.
Thanks everyone for your feedback! Thanks everyone for your feedback!
</p>`; </p>`
if (releaseNote.image) { if (releaseNote.image) {
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png" content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
alt="Release Image for version ${releaseNote.version}" alt="Release Image for version ${releaseNote.version}"
style="max-width: 30em; width: 100%; border-radius: 0.5rem;" style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
/>`; />`
} }
if (releaseNote.extra) { if (releaseNote.extra) {
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`; content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
} }
content += addReleaseNoteSection( content += addReleaseNoteSection(
"⚠️ Breaking changes", '⚠️ Breaking changes',
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote), releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
); )
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote)); content += addReleaseNoteSection('✓ Fixes', releaseNote.fixes?.map(fixToReleaseNote))
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges); content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
content += addReleaseNoteSection("⭐ Features", releaseNote.features); content += addReleaseNoteSection('⭐ Features', releaseNote.features)
return content; return content
} }
function addReleaseNoteSection(title: string, items?: string[]): string { function addReleaseNoteSection(title: string, items?: string[]): string {
if (!items) { if (!items) {
return ""; return ''
} }
let content = `<h2>${title}</h2>`; let content = `<h2>${title}</h2>`
content += "<ul>"; content += '<ul>'
for (const item of items) { for (const item of items) {
if (item && item.length > 0) { if (item && item.length > 0) {
content += `<li>${item}</li>`; content += `<li>${item}</li>`
} }
} }
content += "</ul>"; content += '</ul>'
return content; return content
} }
function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]) { function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) {
if (typeof fix === "string") { if (typeof fix === 'string') {
return fix; return fix
} }
if (!fix || !fix.description || fix.description.length === 0) { if (!fix || !fix.description || fix.description.length === 0) {
return ""; return ''
} }
let note = fix.description; let note = fix.description
if (fix.issue) { if (fix.issue) {
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`; note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`
} }
return note; return note
} }
function breakingChangeToReleaseNote( function breakingChangeToReleaseNote(
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number], breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number],
) { ) {
if (typeof breakingChange === "string") { if (typeof breakingChange === 'string') {
return breakingChange; return breakingChange
} }
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) { if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
return ""; return ''
} }
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`; return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
} }
function pubDate(date?: Date) { function pubDate(date?: Date) {
const newDate = date ?? new Date(); const newDate = date ?? new Date()
const pieces = newDate.toString().split(" "); const pieces = newDate.toString().split(' ')
const offsetTime = pieces[5].match(/[-+]\d{4}/); const offsetTime = pieces[5].match(/[-+]\d{4}/)
const offset = offsetTime ? offsetTime : pieces[5]; 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(" "); return parts.join(' ')
} }

View file

@ -1,15 +1,15 @@
--- ---
import Community from "~/components/Community.astro"; import Community from '~/components/Community.astro'
import Features from "~/components/Features.astro"; import Features from '~/components/Features.astro'
import Hero from "~/components/Hero.astro"; import Hero from '~/components/Hero.astro'
import Sponsors from "~/components/Sponsors.astro"; import Sponsors from '~/components/Sponsors.astro'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { layout } = getUI(locale); const { layout } = getUI(locale)
--- ---
<Layout <Layout

View file

@ -1,15 +1,15 @@
--- ---
import { ArrowRight, Info } from "lucide-astro"; import { ArrowRight, Info } from 'lucide-astro'
import BackButton from "~/components/BackButton.astro"; import BackButton from '~/components/BackButton.astro'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods"; import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
import { getUI } from "~/utils/i18n"; import { getUI } from '~/utils/i18n'
import { getLocale, getOtherLocales } from "~/utils/i18n"; import { getLocale, getOtherLocales } from '~/utils/i18n'
export async function getStaticPaths() { export async function getStaticPaths() {
const mods = await getAllMods(); const mods = await getAllMods()
return mods.flatMap((mod) => [ return mods.flatMap((mod) => [
...getOtherLocales().map((locale) => ({ ...getOtherLocales().map((locale) => ({
params: { params: {
@ -31,25 +31,25 @@ export async function getStaticPaths() {
locale: undefined, locale: undefined,
}, },
}, },
]); ])
} }
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro // https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
const mod = Astro.props; const mod = Astro.props
const dates = { const dates = {
createdAt: getLocalizedDate(mod.createdAt), createdAt: getLocalizedDate(mod.createdAt),
updatedAt: getLocalizedDate(mod.updatedAt), updatedAt: getLocalizedDate(mod.updatedAt),
}; }
const locale = getLocale(Astro as { params: { locale?: string } }); const locale = getLocale(Astro as { params: { locale?: string } })
const { const {
routes: { routes: {
mods: { slug }, mods: { slug },
}, },
} = getUI(locale); } = getUI(locale)
--- ---
<Layout <Layout

View file

@ -1,20 +1,20 @@
--- ---
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import ModsList from "~/components/ModsList"; import ModsList from '~/components/ModsList'
import { CONSTANT } from "~/constants"; import { CONSTANT } from '~/constants'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { getAllMods } from "~/mods"; import { getAllMods } from '~/mods'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { mods }, routes: { mods },
layout, layout,
} = getUI(locale); } = getUI(locale)
const allMods = (await getAllMods()) || []; const allMods = (await getAllMods()) || []
--- ---
<Layout title={layout.mods.title}> <Layout title={layout.mods.title}>

View file

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

View file

@ -1,18 +1,18 @@
--- ---
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { releaseNotes } from "~/release-notes"; import { releaseNotes } from '~/release-notes'
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n"; import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { routes: {
releaseNotes: { slug }, releaseNotes: { slug },
}, },
} = getUI(locale); } = getUI(locale)
export async function getStaticPaths() { export async function getStaticPaths() {
const i18nPaths = getI18nPaths(); const i18nPaths = getI18nPaths()
return i18nPaths.flatMap(({ params: { locale } }) => [ return i18nPaths.flatMap(({ params: { locale } }) => [
...releaseNotes.map((release) => ({ ...releaseNotes.map((release) => ({
@ -20,13 +20,13 @@ export async function getStaticPaths() {
props: { ...release }, props: { ...release },
})), })),
{ {
params: { slug: "latest", locale }, params: { slug: 'latest', locale },
props: { ...releaseNotes[0] }, props: { ...releaseNotes[0] },
}, },
]); ])
} }
const release = Astro.props; const release = Astro.props
--- ---
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}> <Layout title={slug.title} redirect={`/release-notes#${release.version}`}>

View file

@ -1,20 +1,20 @@
--- ---
import { Modal, ModalBody, ModalHeader } from "free-astro-components"; import { Modal, ModalBody, ModalHeader } from 'free-astro-components'
import { ArrowUp } from "lucide-astro"; import { ArrowUp } from 'lucide-astro'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import ReleaseNoteItem from "~/components/ReleaseNoteItem.astro"; import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
import Layout from "~/layouts/Layout.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"; import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { releaseNotes }, routes: { releaseNotes },
layout, layout,
} = getUI(locale); } = getUI(locale)
--- ---
<Layout title={layout.releaseNotes.title}> <Layout title={layout.releaseNotes.title}>

View file

@ -1,15 +1,15 @@
--- ---
import Features from "~/components/Features.astro"; import Features from '~/components/Features.astro'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { welcome }, routes: { welcome },
layout, layout,
} = getUI(locale); } = getUI(locale)
--- ---
<Layout title={layout.welcome.title} description={layout.welcome.description}> <Layout title={layout.welcome.title} description={layout.welcome.description}>

View file

@ -1,29 +1,29 @@
--- ---
import { ArrowRight } from "lucide-astro"; import { ArrowRight } from 'lucide-astro'
import Button from "~/components/Button.astro"; import Button from '~/components/Button.astro'
import Description from "~/components/Description.astro"; import Description from '~/components/Description.astro'
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"; import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
import Layout from "~/layouts/Layout.astro"; import Layout from '~/layouts/Layout.astro'
import whatsNewVideo from "~/assets/whats-new.mp4"; import whatsNewVideo from '~/assets/whats-new.mp4'
import Video from "~/components/Video.astro"; import Video from '~/components/Video.astro'
import { releaseNotes } from "~/release-notes"; import { releaseNotes } from '~/release-notes'
import whatsNewText from "~/release-notes/whats-new.json"; import whatsNewText from '~/release-notes/whats-new.json'
import { getLocale, getUI } from "~/utils/i18n"; import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n"; export { getStaticPaths } from '~/utils/i18n'
const latestVersion = releaseNotes[0]; const latestVersion = releaseNotes[0]
const locale = getLocale(Astro); const locale = getLocale(Astro)
const { const {
routes: { whatsNew }, routes: { whatsNew },
layout, layout,
} = getUI(locale); } = getUI(locale)
// Just redirect to the release notes if we are in a patch version // 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}`); return Astro.redirect(`/release-notes#${latestVersion.version}`)
} }
--- ---

View file

@ -1,41 +1,41 @@
import releaseNotesStable from "./release-notes/stable.json"; import releaseNotesStable from './release-notes/stable.json'
interface FixWithIssue { interface FixWithIssue {
description: string; description: string
issue?: number; issue?: number
} }
type Fix = string | FixWithIssue; type Fix = string | FixWithIssue
export type BreakingChange = string | { description: string; link: string }; export type BreakingChange = string | { description: string; link: string }
export interface ReleaseNote { export interface ReleaseNote {
version: string; version: string
date?: string; // optional for twilight date?: string // optional for twilight
extra?: string; extra?: string
image?: boolean; image?: boolean
fixes?: Fix[]; fixes?: Fix[]
features?: string[]; features?: string[]
breakingChanges?: BreakingChange[]; breakingChanges?: BreakingChange[]
themeChanges?: string[]; themeChanges?: string[]
inProgress?: boolean; inProgress?: boolean
workflowId?: number; workflowId?: number
isTwilight?: boolean; isTwilight?: boolean
} }
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse(); export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse()
export { default as releaseNotesTwilight } from "./release-notes/twilight.json"; 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 // Check if "firefox" is on the feature list
for (const feature of releaseNote.features || []) { for (const feature of releaseNote.features || []) {
if (feature.toLowerCase().includes("firefox")) { if (feature.toLowerCase().includes('firefox')) {
// may be X or X.X or X.X.X // may be X or X.X or X.X.X
const match = feature.match(/(\d+(\.\d+){0,2})/); const match = feature.match(/(\d+(\.\d+){0,2})/)
if (match) { if (match) {
return match[0]; return match[0]
} }
} }
} }
return null; return null
} }

View file

@ -3,25 +3,25 @@
* Returns a mapping from filename to checksum. * Returns a mapping from filename to checksum.
*/ */
export async function getChecksums() { 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: { headers: {
Accept: "application/vnd.github+json", Accept: 'application/vnd.github+json',
"X-GitHub-Api-Version": "2022-11-28", 'X-GitHub-Api-Version': '2022-11-28',
"User-Agent": "zen-browser-checksum-fetcher", '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 data = await res.json()
const body = data.body as string; const body = data.body as string
// Extract the checksum block // Extract the checksum block
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/); const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
const checksums: Record<string, string> = {}; const checksums: Record<string, string> = {}
if (match?.[1]) { if (match?.[1]) {
for (const line of match[1].split("\n")) { for (const line of match[1].split('\n')) {
const [hash, filename] = line.trim().split(/\s+/, 2); const [hash, filename] = line.trim().split(/\s+/, 2)
if (hash && filename) checksums[filename] = hash; if (hash && filename) checksums[filename] = hash
} }
} }
return checksums; return checksums
} }

View file

@ -1,12 +1,12 @@
import type { GetStaticPaths } from "astro"; import type { GetStaticPaths } from 'astro'
import { CONSTANT } from "~/constants"; import { CONSTANT } from '~/constants'
import UI_EN from "~/i18n/en/translation.json"; import UI_EN from '~/i18n/en/translation.json'
/** /**
* Represents the available locales in the application * Represents the available locales in the application
* @typedef {string} Locale * @typedef {string} Locale
*/ */
export type Locale = (typeof locales)[number]; export type Locale = (typeof locales)[number]
/** /**
* Generates a localized path by prefixing the locale if necessary * Generates a localized path by prefixing the locale if necessary
@ -15,10 +15,10 @@ export type Locale = (typeof locales)[number];
*/ */
export const getPath = (locale?: Locale) => (path: string) => { export const getPath = (locale?: Locale) => (path: string) => {
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) { if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`; return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
}
return path
} }
return path;
};
/** /**
* Extracts the current locale from Astro's params, defaulting to the default locale * Extracts the current locale from Astro's params, defaulting to the default locale
@ -29,16 +29,16 @@ export const getPath = (locale?: Locale) => (path: string) => {
*/ */
export const getLocale = (Astro: { params?: { locale?: string } }) => { export const getLocale = (Astro: { params?: { locale?: string } }) => {
if (Astro.params?.locale) { if (Astro.params?.locale) {
return Astro.params.locale as Locale; return Astro.params.locale as Locale
}
return CONSTANT.I18N.DEFAULT_LOCALE as Locale
} }
return CONSTANT.I18N.DEFAULT_LOCALE as Locale;
};
/** /**
* List of all supported locales * List of all supported locales
* @type {Locale[]} * @type {Locale[]}
*/ */
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value); export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value)
/** /**
* List of locales excluding the default locale * List of locales excluding the default locale
@ -46,25 +46,25 @@ export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value);
*/ */
const otherLocales = CONSTANT.I18N.LOCALES.filter( const otherLocales = CONSTANT.I18N.LOCALES.filter(
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE, ({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
); )
/** /**
* Retrieves locales other than the default locale * Retrieves locales other than the default locale
* @returns {Locale[]} Array of non-default locales * @returns {Locale[]} Array of non-default locales
*/ */
export const getOtherLocales = () => otherLocales; export const getOtherLocales = () => otherLocales
/** /**
* Type definition for UI translations based on the English translation * Type definition for UI translations based on the English translation
* @typedef {Object} UI * @typedef {Object} UI
*/ */
export type UI = typeof UI_EN; export type UI = typeof UI_EN
/** /**
* Mapping of locales to their UI translation objects * Mapping of locales to their UI translation objects
* @type {Object.<Locale, UI>} * @type {Object.<Locale, UI>}
*/ */
export const ui = { en: UI_EN }; export const ui = { en: UI_EN }
/** /**
* Retrieves UI translations for a given locale, merging with default translations * Retrieves UI translations for a given locale, merging with default translations
@ -72,9 +72,9 @@ export const ui = { en: UI_EN };
* @returns {UI} Merged UI translations * @returns {UI} Merged UI translations
*/ */
export const getUI = (locale?: Locale | string): UI => { 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 defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE]
const localeUI = ui[validLocale as Locale]; const localeUI = ui[validLocale as Locale]
/** /**
* Recursively merges two objects, with the override object taking precedence * Recursively merges two objects, with the override object taking precedence
@ -85,51 +85,51 @@ export const getUI = (locale?: Locale | string): UI => {
*/ */
function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T { function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T {
// Handle non-object cases // Handle non-object cases
if (typeof defaultObj !== "object" || defaultObj === null) { if (typeof defaultObj !== 'object' || defaultObj === null) {
return (overrideObj ?? defaultObj) as T; return (overrideObj ?? defaultObj) as T
} }
if (typeof overrideObj !== "object" || overrideObj === null) { if (typeof overrideObj !== 'object' || overrideObj === null) {
return (overrideObj ?? defaultObj) as T; return (overrideObj ?? defaultObj) as T
} }
// Create a new object or array based on the default object's type // Create a new object or array based on the default object's type
const result = Array.isArray(defaultObj) ? [...defaultObj] : { ...defaultObj }; const result = Array.isArray(defaultObj) ? [...defaultObj] : { ...defaultObj }
// Merge properties from the default object // Merge properties from the default object
for (const key of Object.keys(defaultObj) as Array<keyof T>) { for (const key of Object.keys(defaultObj) as Array<keyof T>) {
const defaultValue = defaultObj[key]; const defaultValue = defaultObj[key]
const overrideValue = overrideObj[key]; const overrideValue = overrideObj[key]
// Recursively merge nested objects // Recursively merge nested objects
if ( if (
defaultValue !== null && defaultValue !== null &&
overrideValue !== null && overrideValue !== null &&
typeof defaultValue === "object" && typeof defaultValue === 'object' &&
typeof overrideValue === "object" typeof overrideValue === 'object'
) { ) {
// Type assertion to handle nested merging // Type assertion to handle nested merging
(result as Record<keyof T, unknown>)[key] = deepMerge( ;(result as Record<keyof T, unknown>)[key] = deepMerge(
defaultValue as object, defaultValue as object,
overrideValue as Partial<object>, overrideValue as Partial<object>,
); )
} else if (overrideValue !== undefined) { } else if (overrideValue !== undefined) {
// Override with the new value if it exists // Override with the new value if it exists
(result as Record<keyof T, unknown>)[key] = overrideValue; ;(result as Record<keyof T, unknown>)[key] = overrideValue
} }
} }
// Add any new properties from overrideObj // Add any new properties from overrideObj
for (const key of Object.keys(overrideObj) as Array<keyof T>) { for (const key of Object.keys(overrideObj) as Array<keyof T>) {
if (!(key in defaultObj)) { if (!(key in defaultObj)) {
(result as Record<keyof T, unknown>)[key] = overrideObj[key]; ;(result as Record<keyof T, unknown>)[key] = overrideObj[key]
} }
} }
return result as T; return result as T
} }
return deepMerge(defaultUI, localeUI); return deepMerge(defaultUI, localeUI)
}; }
/** /**
* Generates static paths for internationalization * Generates static paths for internationalization
@ -150,13 +150,13 @@ export const getStaticPaths = (() => {
}, },
}), }),
), ),
]; ]
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths
/** /**
* Retrieves all available locales, including both default and non-default * Retrieves all available locales, including both default and non-default
* @returns {Locale[]} Combined array of all locales * @returns {Locale[]} Combined array of all locales
*/ */
export const getLocales = () => { export const getLocales = () => {
return [...locales, ...otherLocales]; return [...locales, ...otherLocales]
}; }