mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 17:05:32 +02:00
chore(prettier): format fiels with prettier
This commit is contained in:
parent
193c159db5
commit
e068816f18
36 changed files with 1261 additions and 1256 deletions
|
@ -1,21 +1,21 @@
|
|||
import tailwind from "@astrojs/tailwind";
|
||||
import tailwind from '@astrojs/tailwind'
|
||||
// @ts-check
|
||||
import { defineConfig } from "astro/config";
|
||||
import { defineConfig } from 'astro/config'
|
||||
|
||||
import preact from "@astrojs/preact";
|
||||
import preact from '@astrojs/preact'
|
||||
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind(), preact({ compat: true }), sitemap({})],
|
||||
site: "https://zen-browser.app",
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en"],
|
||||
routing: {
|
||||
fallbackType: "rewrite",
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
integrations: [tailwind(), preact({ compat: true }), sitemap()],
|
||||
site: 'https://zen-browser.app',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
routing: {
|
||||
fallbackType: 'rewrite',
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
import { ArrowLeft } from "lucide-astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
import { ArrowLeft } from 'lucide-astro'
|
||||
import { getLocale, getUI } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: {
|
||||
mods: { slug },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
mods: { slug },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<button
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
---
|
||||
import { getLocale, getPath } from "~/utils/i18n";
|
||||
import { getLocale, getPath } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
class: className,
|
||||
isPrimary,
|
||||
isAlert,
|
||||
isBordered,
|
||||
href,
|
||||
id,
|
||||
extra,
|
||||
} = Astro.props;
|
||||
class: className,
|
||||
isPrimary,
|
||||
isAlert,
|
||||
isBordered,
|
||||
href,
|
||||
id,
|
||||
extra,
|
||||
} = Astro.props
|
||||
---
|
||||
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
const { white, multiplier = 0.9, class: classList } = Astro.props;
|
||||
const sizes = [216, 396, 576, 756];
|
||||
const borderWidths = [20, 30, 40, 50];
|
||||
const { white, multiplier = 0.9, class: classList } = Astro.props
|
||||
const sizes = [216, 396, 576, 756]
|
||||
const borderWidths = [20, 30, 40, 50]
|
||||
---
|
||||
|
||||
<div
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
import Image from "astro/components/Image.astro";
|
||||
import { Check, Github } from "lucide-astro";
|
||||
import { motion } from "motion/react";
|
||||
import { getTitleAnimation } from "~/animations";
|
||||
import ComImage from "~/assets/ComImage.png";
|
||||
import Button from "~/components/Button.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
import Image from 'astro/components/Image.astro'
|
||||
import { Check, Github } from 'lucide-astro'
|
||||
import { motion } from 'motion/react'
|
||||
import { getTitleAnimation } from '~/animations'
|
||||
import ComImage from '~/assets/ComImage.png'
|
||||
import Button from '~/components/Button.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import { getLocale, getUI } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: {
|
||||
index: { community },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
index: { community },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<section
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
---
|
||||
import { motion } from "motion/react";
|
||||
import { getTitleAnimation } from "~/animations";
|
||||
import Description from "~/components/Description.astro";
|
||||
import { motion } from 'motion/react'
|
||||
import { getTitleAnimation } from '~/animations'
|
||||
import Description from '~/components/Description.astro'
|
||||
|
||||
import CompactModeVideo from "~/assets/CompactMode.webm";
|
||||
import GlanceVideo from "~/assets/Glance.webm";
|
||||
import SplitViewsVideo from "~/assets/SplitViews.webm";
|
||||
import WorkspacesVideo from "~/assets/Workspaces.webm";
|
||||
import CompactModeVideo from '~/assets/CompactMode.webm'
|
||||
import GlanceVideo from '~/assets/Glance.webm'
|
||||
import SplitViewsVideo from '~/assets/SplitViews.webm'
|
||||
import WorkspacesVideo from '~/assets/Workspaces.webm'
|
||||
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
import Video from "./Video.astro";
|
||||
import { getLocale, getUI } from '~/utils/i18n'
|
||||
import Video from './Video.astro'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: {
|
||||
index: { features },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
index: { features },
|
||||
},
|
||||
} = getUI(locale)
|
||||
|
||||
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,
|
||||
);
|
||||
(tab) => tab.description,
|
||||
)
|
||||
---
|
||||
|
||||
<section
|
||||
|
@ -180,12 +180,12 @@ const descriptions = Object.values(features.featureTabs).map(
|
|||
|
||||
<script>
|
||||
const features = document.querySelectorAll(
|
||||
'.feature, .feature-tab'
|
||||
'.feature, .feature-tab',
|
||||
) as NodeListOf<HTMLElement>
|
||||
|
||||
// Set initial description
|
||||
const descriptionEl = document.querySelector(
|
||||
'.feature-description'
|
||||
'.feature-description',
|
||||
) as HTMLDivElement
|
||||
const descriptions = descriptionEl?.dataset.descriptions?.split(',')
|
||||
if (descriptionEl && descriptions) {
|
||||
|
@ -219,7 +219,7 @@ const descriptions = Object.values(features.featureTabs).map(
|
|||
}
|
||||
|
||||
const videos = document.querySelectorAll(
|
||||
'.feature-video'
|
||||
'.feature-video',
|
||||
) as NodeListOf<HTMLVideoElement>
|
||||
videos.forEach((vid, i) => {
|
||||
const yOffset = (i - index) * 20
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
---
|
||||
import { ArrowRight } from "lucide-astro";
|
||||
import Button from "~/components/Button.astro";
|
||||
import Circles from "~/components/Circles.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
import { ArrowRight } from 'lucide-astro'
|
||||
import Button from '~/components/Button.astro'
|
||||
import Circles from '~/components/Circles.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
components: { footer },
|
||||
} = getUI(locale);
|
||||
components: { footer },
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<footer
|
||||
|
@ -24,7 +24,9 @@ const {
|
|||
class="w-full text-center lg:w-1/2 lg:text-left"
|
||||
aria-labelledby="footer-title"
|
||||
>
|
||||
<Description id="footer-title" class="!text-paper text-6xl font-bold">{footer.title}</Title>
|
||||
<Description id="footer-title" class="text-6xl font-bold !text-paper"
|
||||
>{footer.title}</Description
|
||||
>
|
||||
<Description class="mx-auto max-w-xl lg:mx-0">
|
||||
{footer.description}
|
||||
</Description>
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
---
|
||||
import { ArrowRight } from "lucide-astro";
|
||||
import { motion } from "motion/react";
|
||||
import { getTitleAnimation } from "~/animations";
|
||||
import HomePageVideo from "~/assets/HomePageVideo.webm";
|
||||
import Button from "~/components/Button.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import Title from "~/components/Title.astro";
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
import SocialMediaStrip from "./SocialMediaStrip.astro";
|
||||
import Video from "./Video.astro";
|
||||
import { ArrowRight } from 'lucide-astro'
|
||||
import { motion } from 'motion/react'
|
||||
import { getTitleAnimation } from '~/animations'
|
||||
import HomePageVideo from '~/assets/HomePageVideo.webm'
|
||||
import Button from '~/components/Button.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import Title from '~/components/Title.astro'
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
import SocialMediaStrip from './SocialMediaStrip.astro'
|
||||
import Video from './Video.astro'
|
||||
|
||||
let titleAnimationCounter = 0;
|
||||
let titleAnimationCounter = 0
|
||||
function getNewAnimationDelay() {
|
||||
titleAnimationCounter++;
|
||||
return titleAnimationCounter * 0.15;
|
||||
titleAnimationCounter++
|
||||
return titleAnimationCounter * 0.15
|
||||
}
|
||||
|
||||
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 {
|
||||
routes: {
|
||||
index: { hero },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
index: { hero },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<header
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
components: {
|
||||
nav: { menu },
|
||||
},
|
||||
} = getUI(locale);
|
||||
components: {
|
||||
nav: { menu },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<!-- Hidden checkbox for menu toggle -->
|
||||
|
|
|
@ -1,245 +1,241 @@
|
|||
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faSort,
|
||||
faSortDown,
|
||||
faSortUp,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { useModsSearch } from "~/hooks/useModsSearch";
|
||||
import type { ZenTheme } from "~/mods";
|
||||
import { type Locale, getUI } from "~/utils/i18n";
|
||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useModsSearch } from '~/hooks/useModsSearch'
|
||||
import type { ZenTheme } from '~/mods'
|
||||
import { type Locale, getUI } from '~/utils/i18n'
|
||||
|
||||
// Add icons to the library
|
||||
library.add(faSort, faSortUp, faSortDown);
|
||||
library.add(faSort, faSortUp, faSortDown)
|
||||
|
||||
// Create icon objects
|
||||
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" });
|
||||
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" });
|
||||
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" });
|
||||
const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
|
||||
const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
|
||||
const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
|
||||
|
||||
interface ModsListProps {
|
||||
allMods: ZenTheme[];
|
||||
locale: Locale;
|
||||
allMods: ZenTheme[]
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||
const {
|
||||
search,
|
||||
createdSort,
|
||||
updatedSort,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
totalItems,
|
||||
setSearch,
|
||||
toggleCreatedSort,
|
||||
toggleUpdatedSort,
|
||||
setPage,
|
||||
setLimit,
|
||||
mods: paginatedMods,
|
||||
// searchParams,
|
||||
} = useModsSearch(allMods);
|
||||
const {
|
||||
search,
|
||||
createdSort,
|
||||
updatedSort,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
totalItems,
|
||||
setSearch,
|
||||
toggleCreatedSort,
|
||||
toggleUpdatedSort,
|
||||
setPage,
|
||||
setLimit,
|
||||
mods: paginatedMods,
|
||||
// searchParams,
|
||||
} = useModsSearch(allMods)
|
||||
|
||||
const [pageInput, setPageInput] = useState(page.toString());
|
||||
const [pageInput, setPageInput] = useState(page.toString())
|
||||
|
||||
// Keep page input in sync with actual page
|
||||
useEffect(() => {
|
||||
setPageInput(page.toString());
|
||||
}, [page]);
|
||||
// Keep page input in sync with actual page
|
||||
useEffect(() => {
|
||||
setPageInput(page.toString())
|
||||
}, [page])
|
||||
|
||||
function getSortIcon(state: "default" | "asc" | "desc") {
|
||||
if (state === "asc") return ascSortIcon;
|
||||
if (state === "desc") return descSortIcon;
|
||||
return defaultSortIcon;
|
||||
}
|
||||
function getSortIcon(state: 'default' | 'asc' | 'desc') {
|
||||
if (state === 'asc') return ascSortIcon
|
||||
if (state === 'desc') return descSortIcon
|
||||
return defaultSortIcon
|
||||
}
|
||||
|
||||
function handleSearch(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
setSearch(target.value);
|
||||
}
|
||||
function handleSearch(e: Event) {
|
||||
const target = e.target as HTMLInputElement
|
||||
setSearch(target.value)
|
||||
}
|
||||
|
||||
function handleLimitChange(e: Event) {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
setLimit(Number.parseInt(target.value, 10));
|
||||
}
|
||||
function handleLimitChange(e: Event) {
|
||||
const target = e.target as HTMLSelectElement
|
||||
setLimit(Number.parseInt(target.value, 10))
|
||||
}
|
||||
|
||||
function handlePageSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
const newPage = Number.parseInt(pageInput, 10);
|
||||
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
||||
setPage(newPage);
|
||||
window.scrollTo(0, 0);
|
||||
} else {
|
||||
setPageInput(page.toString());
|
||||
}
|
||||
}
|
||||
function handlePageSubmit(e: Event) {
|
||||
e.preventDefault()
|
||||
const newPage = Number.parseInt(pageInput, 10)
|
||||
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
||||
setPage(newPage)
|
||||
window.scrollTo(0, 0)
|
||||
} else {
|
||||
setPageInput(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageInputChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
setPageInput(target.value);
|
||||
}
|
||||
function handlePageInputChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement
|
||||
setPageInput(target.value)
|
||||
}
|
||||
|
||||
function navigatePage(pageNum: number) {
|
||||
setPage(pageNum);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
function navigatePage(pageNum: number) {
|
||||
setPage(pageNum)
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
const {
|
||||
routes: { mods },
|
||||
} = getUI(locale);
|
||||
const {
|
||||
routes: { mods },
|
||||
} = getUI(locale)
|
||||
|
||||
function renderPagination() {
|
||||
if (totalPages <= 1) return null;
|
||||
return (
|
||||
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
<
|
||||
</button>
|
||||
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
||||
{mods.pagination.pagination.split("{input}").map((value, index) => {
|
||||
if (index === 0) {
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
value={pageInput}
|
||||
onInput={handlePageInputChange}
|
||||
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
||||
aria-label="Page number"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="text-sm">
|
||||
{value
|
||||
.replace("{totalPages}", totalPages.toString())
|
||||
.replace("{totalItems}", totalItems.toString())}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</form>
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function renderPagination() {
|
||||
if (totalPages <= 1) return null
|
||||
return (
|
||||
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
||||
<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'
|
||||
}`}
|
||||
>
|
||||
<
|
||||
</button>
|
||||
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
||||
{mods.pagination.pagination.split('{input}').map((value, index) => {
|
||||
if (index === 0) {
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
value={pageInput}
|
||||
onInput={handlePageInputChange}
|
||||
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
||||
aria-label="Page number"
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span className="text-sm">
|
||||
{value
|
||||
.replace('{totalPages}', totalPages.toString())
|
||||
.replace('{totalItems}', totalItems.toString())}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</form>
|
||||
<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'
|
||||
}`}
|
||||
>
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mx-auto flex flex-col items-start gap-4 px-8 lg:w-1/2">
|
||||
<div className="flex w-full flex-col items-center gap-6">
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
className="w-full rounded-full border-2 border-dark bg-transparent px-6 py-2 text-lg outline-none"
|
||||
placeholder={mods.search}
|
||||
value={search}
|
||||
onInput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<div>
|
||||
<div className="mx-auto flex flex-col items-start gap-4 px-8 lg:w-1/2">
|
||||
<div className="flex w-full flex-col items-center gap-6">
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
className="w-full rounded-full border-2 border-dark bg-transparent px-6 py-2 text-lg outline-none"
|
||||
placeholder={mods.search}
|
||||
value={search}
|
||||
onInput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid w-full grid-cols-2 place-items-center gap-4 sm:grid-cols-3">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleCreatedSort}
|
||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||
>
|
||||
{mods.sort.lastCreated}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getSortIcon(createdSort).html[0],
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid w-full grid-cols-2 place-items-center gap-4 sm:grid-cols-3">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleCreatedSort}
|
||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||
>
|
||||
{mods.sort.lastCreated}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getSortIcon(createdSort).html[0],
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleUpdatedSort}
|
||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||
>
|
||||
{mods.sort.lastUpdated}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getSortIcon(updatedSort).html[0],
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleUpdatedSort}
|
||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||
>
|
||||
{mods.sort.lastUpdated}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getSortIcon(updatedSort).html[0],
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-4 py-2">
|
||||
<label htmlFor="limit" className="text-md font-semibold">
|
||||
{mods.sort.perPage}
|
||||
</label>
|
||||
<select
|
||||
id="limit"
|
||||
value={limit}
|
||||
onInput={handleLimitChange}
|
||||
className="rounded border border-dark bg-transparent px-2 py-1 text-sm [&>option]:text-black"
|
||||
>
|
||||
<option value="12">12</option>
|
||||
<option value="24">24</option>
|
||||
<option value="48">48</option>
|
||||
<option value="96">96</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-4 py-2">
|
||||
<label htmlFor="limit" className="text-md font-semibold">
|
||||
{mods.sort.perPage}
|
||||
</label>
|
||||
<select
|
||||
id="limit"
|
||||
value={limit}
|
||||
onInput={handleLimitChange}
|
||||
className="rounded border border-dark bg-transparent px-2 py-1 text-sm [&>option]:text-black"
|
||||
>
|
||||
<option value="12">12</option>
|
||||
<option value="24">24</option>
|
||||
<option value="48">48</option>
|
||||
<option value="96">96</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto grid grid-cols-1 place-items-start gap-12 p-10 md:grid-cols-2 lg:grid-cols-3 lg:p-24 lg:px-24 2xl:grid-cols-4">
|
||||
{paginatedMods.length > 0 ? (
|
||||
paginatedMods.map((mod) => (
|
||||
<a
|
||||
key={mod.id}
|
||||
href={`/mods/${mod.id}`}
|
||||
className="flex flex-col gap-4 border-transparent transition-colors duration-100 hover:opacity-90"
|
||||
>
|
||||
<div className="relative mb-0 block aspect-[1.85/1] h-48 overflow-hidden rounded-md border-2 border-dark object-cover shadow-md">
|
||||
<img
|
||||
src={mod.image}
|
||||
alt={mod.name}
|
||||
loading="lazy"
|
||||
className="h-full w-full object-cover transition-transform duration-100 hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold">
|
||||
{mod.name}{" "}
|
||||
<span className="ml-1 text-sm font-normal">
|
||||
by @{mod.author}
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-sm font-thin">{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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mx-auto grid grid-cols-1 place-items-start gap-12 p-10 md:grid-cols-2 lg:grid-cols-3 lg:p-24 lg:px-24 2xl:grid-cols-4">
|
||||
{paginatedMods.length > 0 ? (
|
||||
paginatedMods.map((mod) => (
|
||||
<a
|
||||
key={mod.id}
|
||||
href={`/mods/${mod.id}`}
|
||||
className="flex flex-col gap-4 border-transparent transition-colors duration-100 hover:opacity-90"
|
||||
>
|
||||
<div className="relative mb-0 block aspect-[1.85/1] h-48 overflow-hidden rounded-md border-2 border-dark object-cover shadow-md">
|
||||
<img
|
||||
src={mod.image}
|
||||
alt={mod.name}
|
||||
loading="lazy"
|
||||
className="h-full w-full object-cover transition-transform duration-100 hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold">
|
||||
{mod.name}{' '}
|
||||
<span className="ml-1 text-sm font-normal">
|
||||
by @{mod.author}
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-sm font-thin">{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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{renderPagination()}
|
||||
</div>
|
||||
);
|
||||
{renderPagination()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
---
|
||||
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar";
|
||||
import { ArrowRight, ChevronDown, Download, Menu } from "lucide-astro";
|
||||
import { motion } from "motion/react";
|
||||
import Button from "~/components/Button.astro";
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
import { getTitleAnimation } from "../animations.ts";
|
||||
import Logo from "./Logo.astro";
|
||||
import MobileMenu from "./MobileMenu.astro";
|
||||
import ThemeSwitch from "./ThemeSwitch.astro";
|
||||
import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
|
||||
import { ArrowRight, ChevronDown, Download, Menu } from 'lucide-astro'
|
||||
import { motion } from 'motion/react'
|
||||
import Button from '~/components/Button.astro'
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
import { getTitleAnimation } from '../animations.ts'
|
||||
import Logo from './Logo.astro'
|
||||
import MobileMenu from './MobileMenu.astro'
|
||||
import ThemeSwitch from './ThemeSwitch.astro'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
components: {
|
||||
nav: { brand, menu },
|
||||
},
|
||||
} = getUI(locale);
|
||||
components: {
|
||||
nav: { brand, menu },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
---
|
||||
import { Accordion, AccordionItem } from "free-astro-components";
|
||||
import { Info } from "lucide-astro";
|
||||
import { Accordion, AccordionItem } from 'free-astro-components'
|
||||
import { Info } from 'lucide-astro'
|
||||
|
||||
import { releaseNotes as releaseNotesData } from "~/release-notes";
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
import { releaseNotes as releaseNotesData } from '~/release-notes'
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
import {
|
||||
type BreakingChange,
|
||||
type ReleaseNote,
|
||||
getReleaseNoteFirefoxVersion,
|
||||
} from "../release-notes";
|
||||
export type Props = ReleaseNote;
|
||||
const { isTwilight, ...props } = Astro.props;
|
||||
type BreakingChange,
|
||||
type ReleaseNote,
|
||||
getReleaseNoteFirefoxVersion,
|
||||
} from '../release-notes'
|
||||
export type Props = ReleaseNote
|
||||
const { isTwilight, ...props } = Astro.props
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
routes: {
|
||||
releaseNotes: {
|
||||
components: { releaseNoteItem },
|
||||
},
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
releaseNotes: {
|
||||
components: { releaseNoteItem },
|
||||
},
|
||||
},
|
||||
} = getUI(locale)
|
||||
|
||||
let date;
|
||||
let date
|
||||
if (props.date) {
|
||||
const [day, month, year] = props.date.split("/");
|
||||
date = new Date(Date.parse(`${year}-${month}-${day}`));
|
||||
const [day, month, year] = props.date.split('/')
|
||||
date = new Date(Date.parse(`${year}-${month}-${day}`))
|
||||
}
|
||||
|
||||
const ffVersion = getReleaseNoteFirefoxVersion(props);
|
||||
const ffVersion = getReleaseNoteFirefoxVersion(props)
|
||||
const currentReleaseIndex = releaseNotesData.findIndex(
|
||||
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
||||
);
|
||||
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1];
|
||||
let compareLink = "";
|
||||
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
||||
)
|
||||
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
|
||||
let compareLink = ''
|
||||
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}`
|
||||
}
|
||||
---
|
||||
|
||||
|
@ -62,7 +62,10 @@ if (prevReleaseNote && !isTwilight) {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
{releaseNoteItem.releaseChanges.replaceAll("{version}", props.version)}
|
||||
{releaseNoteItem.releaseChanges.replaceAll(
|
||||
'{version}',
|
||||
props.version,
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -152,7 +155,7 @@ if (prevReleaseNote && !isTwilight) {
|
|||
target="_blank"
|
||||
aria-label={releaseNoteItem.viewIssue.replace(
|
||||
'{issue}',
|
||||
fix.issue
|
||||
fix.issue,
|
||||
)}
|
||||
>
|
||||
#{fix.issue}
|
||||
|
|
|
@ -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 {
|
||||
faBluesky,
|
||||
faGithub,
|
||||
faMastodon,
|
||||
faReddit,
|
||||
faXTwitter,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
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" });
|
||||
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" });
|
||||
const Github = icon({ prefix: "fab", iconName: "github" });
|
||||
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" });
|
||||
const Reddit = icon({ prefix: "fab", iconName: "reddit" });
|
||||
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
||||
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
||||
const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
|
||||
const Github = icon({ prefix: 'fab', iconName: 'github' })
|
||||
const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
|
||||
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
||||
---
|
||||
|
||||
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
---
|
||||
import { motion } from "motion/react";
|
||||
import { getTitleAnimation } from "~/animations";
|
||||
import Description from "~/components/Description.astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
import { motion } from 'motion/react'
|
||||
import { getTitleAnimation } from '~/animations'
|
||||
import Description from '~/components/Description.astro'
|
||||
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";
|
||||
const { showSponsors = true } = Astro.props;
|
||||
import Image from 'astro/components/Image.astro'
|
||||
const { showSponsors = true } = Astro.props
|
||||
|
||||
const {
|
||||
routes: {
|
||||
index: { sponsors },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
index: { sponsors },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
interface Props {
|
||||
label?: string;
|
||||
className?: string;
|
||||
label?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const { label, className = "" } = Astro.props;
|
||||
const { label, className = '' } = Astro.props
|
||||
---
|
||||
|
||||
<button
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
const { class: className } = Astro.props;
|
||||
const { class: className } = Astro.props
|
||||
---
|
||||
|
||||
<h1 class:list={['title text-dark', className]}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
const { src, class: className, ...rest } = Astro.props;
|
||||
const type = src.split(".").pop() || "webm";
|
||||
const { src, class: className, ...rest } = Astro.props
|
||||
const type = src.split('.').pop() || 'webm'
|
||||
---
|
||||
|
||||
<video
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const I18N = {
|
||||
DEFAULT_LOCALE: "en",
|
||||
LOCALES: [{ label: "English", value: "en" }],
|
||||
} as const;
|
||||
DEFAULT_LOCALE: 'en',
|
||||
LOCALES: [{ label: 'English', value: 'en' }],
|
||||
} as const
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { I18N } from "./i18n";
|
||||
import { I18N } from './i18n'
|
||||
|
||||
export const CONSTANT = {
|
||||
I18N,
|
||||
};
|
||||
I18N,
|
||||
}
|
||||
|
|
|
@ -1,467 +1,467 @@
|
|||
{
|
||||
"routes": {
|
||||
"index": {
|
||||
"title": "Zen Browser",
|
||||
"hero": {
|
||||
"title": ["welcome", "to", "a", "calmer", "internet"],
|
||||
"description": [
|
||||
"Beautifully designed, privacy-focused, and packed with features.",
|
||||
"We care about your experience, not your data."
|
||||
],
|
||||
"buttons": {
|
||||
"beta": "Beta is now available!",
|
||||
"support": "Support Us ❤️"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"title1": "Productivity",
|
||||
"title2": "at",
|
||||
"title3": "its best",
|
||||
"description": "Zen Browser is packed with features that help you stay productive and focused. Browsers should be tools that help you get things done, not distractions that keep you from your work.",
|
||||
"featureTabs": {
|
||||
"workspaces": {
|
||||
"title": "Workspaces",
|
||||
"description": "Organize your tabs into Workspaces to keep your projects separate and organized, and switch between them with ease."
|
||||
},
|
||||
"compactMode": {
|
||||
"title": "Compact Mode",
|
||||
"description": "Zen's Compact Mode gives you more screen real estate by hiding the tab bar when you don't need it, and showing it when you do."
|
||||
},
|
||||
"glance": {
|
||||
"title": "Glance",
|
||||
"description": "Glance allows you to quickly switch between your most used tabs, without having to scroll through your history."
|
||||
},
|
||||
"splitView": {
|
||||
"title": "Split View",
|
||||
"description": "Split View allows you to view two tabs side by side, making it easier to compare and switch between them."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sponsors": {
|
||||
"title": "Our Sponsors",
|
||||
"description": "We are grateful to our sponsors for their support. They help us to keep the project alive.<br />You can also be part of this journey by <a href=\"/donate\" class=\"zen-link\">donating us directly</a>!",
|
||||
"sponsors": {
|
||||
"tuta": {
|
||||
"name": "Tuta",
|
||||
"url": "https://tuta.com/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"title": ["Our", "Core", "Values"],
|
||||
"description": "We make it not only a priority, but a necessity to ensure that Zen always strikes the right balance between beauty, performance, and privacy. We are committed to making Zen the most beautiful, productive, and privacy-respecting browser out there — without compromising on your experience.",
|
||||
"lists": {
|
||||
"freeAndOpenSource": {
|
||||
"title": "Free and open-source",
|
||||
"description": "Zen is free and open-source software, which means you can use it without any cost and can modify it to suit your needs."
|
||||
},
|
||||
"simpleYetPowerful": {
|
||||
"title": "Simple yet powerful",
|
||||
"description": "Zen is simple to use, but powerful enough to handle your daily tasks."
|
||||
},
|
||||
"privateAndAlwaysUpToDate": {
|
||||
"title": "Private and always up-to-date",
|
||||
"description": "Zen is private and always up-to-date, which means you can use it without any cost and can modify it to suit your needs."
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"community": {
|
||||
"alt": "Community"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mods": {
|
||||
"title": "Zen Mods",
|
||||
"description": "Browse our diverse collection of Zen Mods, community-made plugins and themes for Zen Browser. Discover a theme to match every mood, and a plugin to fulfill every requirement. Start customizing your browser experience today!",
|
||||
"pagination": {
|
||||
"pagination": "{input} of {totalPages} ({totalItems} items)"
|
||||
},
|
||||
"search": "Type to search...",
|
||||
"sort": {
|
||||
"lastCreated": "Last created",
|
||||
"lastUpdated": "Last updated",
|
||||
"perPage": "Per page"
|
||||
},
|
||||
"noResults": "No results found",
|
||||
"noResultsDescription": "Try searching for a different term or check back later.",
|
||||
"slug": {
|
||||
"title": "{name} - Zen Mods",
|
||||
"description": "Learn more about {name} mod available on Zen Browser",
|
||||
"alert": {
|
||||
"description": "You need to have Zen Browser installed to install this theme.",
|
||||
"button": "Download now!"
|
||||
},
|
||||
"createdBy": "Created by <a href={link} class=\"zen-link font-bold\">{author}</a> • <span class=\"font-bold\">v{version}</span>",
|
||||
"creationDate": "Creation date • <b>{createdAt}</b>",
|
||||
"latestUpdate": "Latest update • <b>{updatedAt}</b>",
|
||||
"visitModHomepage": "Visit mod homepage",
|
||||
"installMod": "Install Mod 🎉",
|
||||
"uninstallMod": "Uninstall Mod",
|
||||
"back": "Back"
|
||||
}
|
||||
},
|
||||
"releaseNotes": {
|
||||
"title": "Release notes - Zen Browser",
|
||||
"topSection": {
|
||||
"title": "Release Notes",
|
||||
"description": "Stay up to date with the latest changes to Zen Browser! Since the <a class=\"zen-link\" href=\"#1.0.0-a.1\">first release</a> till <a class=\"zen-link\" href=\"#{latestVersion}\">{latestVersion}</a>, we've been working hard to make Zen Browser the best it can be. Thanks everyone for your feedback! ❤️"
|
||||
},
|
||||
"list": {
|
||||
"support": "Give us some support!",
|
||||
"expandAll": "Expand all",
|
||||
"navigateToVersion": "Navigate to version..."
|
||||
},
|
||||
"backToTop": "Back to the top",
|
||||
"chooseVersion": "Choose version",
|
||||
"components": {
|
||||
"releaseNoteItem": {
|
||||
"twilight": "Twilight",
|
||||
"twilightChanges": "Twilight changes for {version} 🌙",
|
||||
"releaseChanges": "Release notes for {version} 🎉",
|
||||
"firefoxVersion": "Firefox {version}",
|
||||
"githubRelease": "GitHub Release",
|
||||
"workflowRun": "Workflow run",
|
||||
"compareChanges": "Compare changes",
|
||||
"twilightWarning": "Please note that Twilight is a pre-release version of Zen Browser. It may contain bugs and unfinished features.",
|
||||
"reportIssues": " If you encounter any issues, please report them on <a rel=\"noopener noreferrer\" target=\"_blank\" href=\"https://github.com/zen-browser/desktop/issues/\" class=\"zen-link\">the issues page</a>.",
|
||||
"sections": {
|
||||
"fixes": "Fixes",
|
||||
"features": "Features",
|
||||
"themeChanges": "Theme Changes",
|
||||
"breakingChanges": {
|
||||
"title": "Breaking Changes",
|
||||
"description": "View breaking changes on GitHub"
|
||||
}
|
||||
},
|
||||
"learnMore": "Learn more",
|
||||
"viewIssue": "View issue number {issue} on GitHub"
|
||||
}
|
||||
},
|
||||
"slug": {
|
||||
"title": "Release notes",
|
||||
"redirect": "Redirecting to release notes for version {version}..."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "About - Zen Browser",
|
||||
"description": "We are simply a group of developers and designers who care about your experience on the web. We believe that the internet should be a place where you can explore, learn, and connect without worrying about your data being collected.",
|
||||
"littleHelp": "A little help?",
|
||||
"mainTeam": {
|
||||
"title": "Main Team",
|
||||
"description": "This list shows the main team members who are working hard to bring you the best browsing experience.",
|
||||
"members": {
|
||||
"mauro": {
|
||||
"name": "Mauro B.",
|
||||
"description": "Creator, Main Developer",
|
||||
"link": "https://cheff.dev/"
|
||||
},
|
||||
"oscar": {
|
||||
"name": "Oscar Gonzalez",
|
||||
"description": "Site Reliability Engineer (SRE) and code signing.",
|
||||
"link": false
|
||||
},
|
||||
"jan": {
|
||||
"name": "Jan Heres",
|
||||
"description": "Active contributor and helps with MacOS builds",
|
||||
"link": "https://janheres.eu/"
|
||||
},
|
||||
"brhm": {
|
||||
"name": "BrhmDev",
|
||||
"description": "Active contributor with great contributions",
|
||||
"link": "https://github.com/BrhmDev"
|
||||
},
|
||||
"canoa": {
|
||||
"name": "Canoa",
|
||||
"description": "Active contributor, and very active in issue handling and website management",
|
||||
"link": "https://thatcanoa.org/"
|
||||
},
|
||||
"adam": {
|
||||
"name": "Adam",
|
||||
"description": "Branding and design",
|
||||
"link": "https://cybrneon.xyz/"
|
||||
},
|
||||
"kristijanribaric": {
|
||||
"name": "Kristijan Ribaric",
|
||||
"description": "Active contributor",
|
||||
"link": "https://github.com/kristijanribaric"
|
||||
},
|
||||
"n7itro": {
|
||||
"name": "n7itro",
|
||||
"description": "Active contributor and release notes writer",
|
||||
"link": "https://github.com/n7itro"
|
||||
},
|
||||
"bryan": {
|
||||
"name": "Bryan Galdámez",
|
||||
"description": "Huge contributor on theme functionalities",
|
||||
"link": "https://josuegalre.netlify.app/"
|
||||
},
|
||||
"jafeth": {
|
||||
"name": "Jafeth Garro",
|
||||
"description": "Documentation writer",
|
||||
"link": "https://iamjafeth.com/"
|
||||
},
|
||||
"larvey": {
|
||||
"name": "Larvey",
|
||||
"description": "AUR maintainer",
|
||||
"link": "https://github.com/LarveyOfficial/"
|
||||
},
|
||||
"daniel": {
|
||||
"name": "Daniel García",
|
||||
"description": "MacOS certificate and app notarization maintainer",
|
||||
"link": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"contributors": {
|
||||
"title": "Contributors",
|
||||
"description": "This list shows the contributors who have helped us to make Zen Browser the best it can be."
|
||||
}
|
||||
},
|
||||
"donate": {
|
||||
"title": "Donate - Zen Browser",
|
||||
"description": "We are a small team of developers working hard to bring you the best browsing experience. If you like what we do, please consider supporting us.",
|
||||
"patreon": {
|
||||
"title": "Patreon",
|
||||
"description": "Patreon allows you to support us with a monthly donation. You can choose the level of support that works best for you."
|
||||
},
|
||||
"koFi": {
|
||||
"title": "Ko-fi",
|
||||
"description": "Ko-fi allows you to support us with a one-time donation. You can choose the amount that works best for you. Monthly donations are also available.",
|
||||
"button": "Go to Ko-fi"
|
||||
}
|
||||
},
|
||||
"download": {
|
||||
"title": "Download - Zen Browser",
|
||||
"description": "Download Zen Browser for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification.",
|
||||
"twilightInfo": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates.",
|
||||
"alertInfo": {
|
||||
"description": "<strong class='font-medium text-zen-blue'>Twilight Mode:</strong> You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
|
||||
},
|
||||
"platformSelector": {
|
||||
"title": "Platform Selector",
|
||||
"description": "Select your platform to download Zen Browser."
|
||||
},
|
||||
"additionalResources": {
|
||||
"title": "Additional Resources",
|
||||
"sourceCode": {
|
||||
"title": "Source Code",
|
||||
"description": "Explore Zen Browser's source code on GitHub. Contribute to the project or build your own version."
|
||||
},
|
||||
"documentation": {
|
||||
"title": "Documentation",
|
||||
"description": "Access comprehensive documentation, guides, and tutorials for Zen Browser."
|
||||
}
|
||||
},
|
||||
"securityNotice": {
|
||||
"title": "Verified & Secure Downloads",
|
||||
"description": "All Zen downloads are signed and verified for your security. We recommend downloading directly from our official website or GitHub repository. If your download seems broken or gets flagged by your antivirus, please <a href='https://github.com/zen-browser/desktop/issues/new/choose' class='zen-link ml-1'>report it to us</a>."
|
||||
},
|
||||
"platformNames": {
|
||||
"mac": "macOS",
|
||||
"windows": "Windows",
|
||||
"linux": "Linux",
|
||||
"macDownload": "MacOS Download",
|
||||
"windowsDownload": "Windows Download",
|
||||
"linuxDownload": "Linux Download"
|
||||
},
|
||||
"platformDescriptions": {
|
||||
"mac": "Works on both new Apple (M-Series) and older Intel Macs.<br />Requires macOS 11.0 or later.",
|
||||
"windows": "Works on Windows 10 and Windows 11.<br />Not sure which version to get? Most people should choose the 64-bit installer.",
|
||||
"linux": "Works with many Linux versions.<br />Pick the download that matches your system."
|
||||
}
|
||||
},
|
||||
"privacyPolicy": {
|
||||
"title": "Privacy Policy",
|
||||
"lastUpdated": "Last updated: 2025-02-5",
|
||||
"sections": {
|
||||
"introduction": {
|
||||
"title": "Introduction",
|
||||
"body": "Welcome to Zen Browser! Your privacy is our priority. This Privacy Policy outlines the types of personal information we collect, how we use it, and the steps we take to protect your data when you use Zen Browser.",
|
||||
"summary": "We don't sell data - We don't collect data - We don't track you"
|
||||
},
|
||||
"noCollect": {
|
||||
"title": "1. Information We Do Not Collect",
|
||||
"body": "Zen Browser is designed with privacy in mind. We do not collect, store, or share any of your personal data. Here's what that means:"
|
||||
},
|
||||
"noTelemetry": {
|
||||
"title": "1.1. No Telemetry",
|
||||
"body": "We do not collect any telemetry data or crash reports.",
|
||||
"body2": "Zen Browser has stripped out telemetry built into Mozilla Firefox. We have removed all telemetry data collection and crash reports."
|
||||
},
|
||||
"noPersonalData": {
|
||||
"title": "1.2. No Personal Data Collection",
|
||||
"body": "Zen Browser does not collect any personal information such as your IP address, browsing history, search queries, or form data."
|
||||
},
|
||||
"noThirdParty": {
|
||||
"title": "1.3. No Third-Party Tracking",
|
||||
"body": "We do not allow third-party trackers or analytics tools to operate within Zen Browser. Your browsing activity remains entirely private and is not shared with any third party. Mozilla is not considered a third party as it is the base of Zen Browser."
|
||||
},
|
||||
"externalConnections": {
|
||||
"title": "1.4. External connections made at startup",
|
||||
"body": "Zen Browser may make external connections at startup to check for updates and ensure the browser is up to date on plugins, addons, check for connectivity and Geolocation/push notifications services in order to comply with web standards. We, at Zen, do not collect any data from these connections, but they may be logged by third-party services or websites you visit. These connections are necessary for the proper functioning of the browser and are not used for tracking or profiling purposes. They can be disabled through the browser flags (about:config)."
|
||||
},
|
||||
"localStorage": {
|
||||
"title": "2. Information Stored Locally on Your Device"
|
||||
},
|
||||
"browsingData": {
|
||||
"title": "2.1. Browsing Data",
|
||||
"body": "Zen Browser stores certain data locally on your device to enhance your browsing experience. This includes:"
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"body": "Cookies are stored locally on your device and are not shared with Zen Browser or any third party. You have full control over the management of cookies through the browser's settings."
|
||||
},
|
||||
"cache": {
|
||||
"title": "Cache and Temporary Files",
|
||||
"body": "Zen Browser may store cache files and other temporary data locally to improve performance. These files can be cleared at any time through the browser's settings."
|
||||
},
|
||||
"settings": {
|
||||
"title": "2.2. Settings and Preferences",
|
||||
"body": "Any customizations, settings, and preferences you make within Zen Browser are stored locally on your device. We do not have access to or control over this data."
|
||||
},
|
||||
"sync": {
|
||||
"title": "3. Sync Feature",
|
||||
"body": "Zen Browser offers a \"Sync\" feature, which is implemented using Mozilla Firefox's Sync feature. This feature allows you to synchronize your bookmarks, history, passwords, and other data across multiple devices. For this feature to work, your data is encrypted and stored on Mozilla's servers and is treated in accordance with their Privacy Policy. We, at Zen, cannot view any of this data.",
|
||||
"link1": "Mozilla Firefox Sync",
|
||||
"link2": "This is how we store your passwords"
|
||||
},
|
||||
"addons": {
|
||||
"title": "4. Add-ons and \"Mods\"",
|
||||
"body": "You can install Add-ons from addons.mozilla.org. Zen Browser periodically checks for updates to these Add-ons.\nYou can also install \"Mods\" from zen-browser.app/mods. These Mods are hosted by our services and follow the same privacy policy our website. We do not collect any data from these Mods, they are purely static content that is downloaded to your device."
|
||||
},
|
||||
"security": {
|
||||
"title": "5. Data Security",
|
||||
"body": "Although Zen Browser does not collect your data, we are committed to protecting the information that is stored locally on your device and, if you use the Sync feature, the encrypted data stored on Mozilla's servers. We recommend that you use secure passwords, enable device encryption, and regularly update your software to ensure your data remains safe.",
|
||||
"note": "Note that most of the security measures are taken care by Mozilla Firefox."
|
||||
},
|
||||
"control": {
|
||||
"title": "6. Your Control",
|
||||
"deletionTitle": "6.1. Data Deletion",
|
||||
"deletionBody": "You have full control over all data stored locally on your device by Zen Browser. You can clear your browsing data, cookies, and cache at any time using the browser's settings."
|
||||
},
|
||||
"website": {
|
||||
"title": "7. Our Website and Services",
|
||||
"body": "Zen Browser's website and services do not use any third-party analytics, tracking, or CDN services. We do not collect any personal information from users visiting our website. The website is hosted on Cloudflare but with analytics and tracking disabled, Cloudflare may collect some analytics data from HTTP requests in order to provide security and performance improvements. However, this data is not linked to any personal information and is not used for tracking purposes.",
|
||||
"externalLinksTitle": "7.1. External links",
|
||||
"externalLinksBody": "Zen Browser may contain links to external websites or services that are not owned or operated by us. We are not responsible for the content or privacy practices of these sites. We recommend that you review the privacy policies of these sites before providing them with any personal information."
|
||||
},
|
||||
"changes": {
|
||||
"title": "8. Changes to This Privacy Policy",
|
||||
"body": "We may update this Privacy Policy from time to time to reflect changes in our practices or legal requirements. We will notify you of any significant changes by updating the effective date at the top of this policy. Continued use of Zen Browser after such changes constitutes your acceptance of the new terms."
|
||||
},
|
||||
"otherTelemetry": {
|
||||
"title": "9. Other telemetry done by Mozilla Firefox",
|
||||
"body": "We try to disable all telemetry data collection in Zen Browser. But, we may have missed some. Check the below links for more information.",
|
||||
"firefoxPrivacyNotice": "Firefox Privacy Notice",
|
||||
"forMoreInformation": "for more information."
|
||||
},
|
||||
"contact": {
|
||||
"title": "10. Contact Us",
|
||||
"body": "If you have any questions or concerns about this Privacy Policy or Zen Browser, please contact us at:",
|
||||
"discord": "Discord: ",
|
||||
"discordLink": "Zen Browser's Discord",
|
||||
"github": "GitHub: ",
|
||||
"githubLink": "Organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": ["Welcome", "to", "Zen!"]
|
||||
},
|
||||
"whatsNew": {
|
||||
"title": "What's New in {latestVersion.version}!",
|
||||
"reportIssue": "Report an issue",
|
||||
"joinDiscord": "Join our Discord",
|
||||
"readFullReleaseNotes": "Read the full release notes"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page Not Found",
|
||||
"description": "Sorry, the page you are looking for does not exist or has been moved.",
|
||||
"button": "Go Home"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"index": {
|
||||
"title": "Zen Browser",
|
||||
"description": "Beautifully designed, privacy-focused, and packed with features."
|
||||
},
|
||||
"mods": {
|
||||
"title": "Zen Mods",
|
||||
"description": "Browse our diverse collection of Zen Mods, community-made plugins and themes for Zen Browser. Discover a theme to match every mood, and a plugin to fulfill every requirement. Start customizing your browser experience today!"
|
||||
},
|
||||
"releaseNotes": {
|
||||
"title": "Release notes - Zen",
|
||||
"description": "Stay up to date with the latest changes to Zen Browser! Since the first release till {latestVersion}, we've been working hard to make Zen Browser the best it can be. Thanks everyone for your feedback! ❤️"
|
||||
},
|
||||
"about": {
|
||||
"title": "About - Zen",
|
||||
"description": "We are simply a group of developers and designers who care about your experience on the web. We believe that the internet should be a place where you can explore, learn, and connect without worrying about your data being collected."
|
||||
},
|
||||
"donate": {
|
||||
"title": "Donate - Zen",
|
||||
"description": "We are a small team of developers working hard to bring you the best browsing experience. If you like what we do, please consider supporting us."
|
||||
},
|
||||
"download": {
|
||||
"title": "Download - Zen",
|
||||
"description": "Download Zen Browser for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification."
|
||||
},
|
||||
"privacyPolicy": {
|
||||
"title": "Privacy Policy - Zen",
|
||||
"description": "Your privacy is our priority. This Privacy Policy outlines the types of personal information we collect, how we use it, and the steps we take to protect your data when you use Zen Browser."
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Welcome!",
|
||||
"description": "Welcome to Zen!"
|
||||
},
|
||||
"whatsNew": {
|
||||
"title": "What's New in {latestVersion.version}!"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"footer": {
|
||||
"title": "Zen Browser",
|
||||
"description": "Beautifully designed, privacy-focused, and packed with features. We care about your experience, not your data.",
|
||||
"download": "Download",
|
||||
"followUs": "Follow Us",
|
||||
"aboutUs": "About Us",
|
||||
"teamAndContributors": "Team & Contributors",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"getStarted": "Get Started",
|
||||
"documentation": "Documentation",
|
||||
"zenMods": "Zen Mods",
|
||||
"releaseNotes": "Release Notes",
|
||||
"getHelp": "Get Help",
|
||||
"discord": "Discord",
|
||||
"uptimeStatus": "Uptime Status",
|
||||
"reportAnIssue": "Report an Issue",
|
||||
"twilight": "Twilight",
|
||||
"madeWith": "Made with <span aria-label='love'>❤️</span> by the <a href='{link}' class='zen-link inline-block font-bold'>Zen Team</a>"
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Zen Browser",
|
||||
"menu": {
|
||||
"gettingStarted": "Getting Started",
|
||||
"usefulLinks": "Useful Links",
|
||||
"mods": "Mods",
|
||||
"download": "Download",
|
||||
"discord": "Discord",
|
||||
"releaseNotes": "Release Notes",
|
||||
"zenMods": "Zen Mods",
|
||||
"tryZenMods": "Try Zen Mods",
|
||||
"zenModsDesc": "Customize your browsing experience with Zen Mods.",
|
||||
"releaseNotesDesc": "Stay up to date with the latest features and improvements.",
|
||||
"discordDesc": "Join our community on Discord to chat with other Zen users!",
|
||||
"donate": "Donate ❤️",
|
||||
"donateDesc": "Support the development of Zen Browser with a donation.",
|
||||
"aboutUs": "About Us 🌟",
|
||||
"aboutUsDesc": "Learn more about the team behind Zen Browser.",
|
||||
"documentation": "Documentation",
|
||||
"documentationDesc": "Learn how to use Zen Browser with our documentation.",
|
||||
"github": "GitHub",
|
||||
"githubDesc": "Contribute to the development of Zen Browser on GitHub.",
|
||||
"menu": "Menu"
|
||||
}
|
||||
}
|
||||
}
|
||||
"routes": {
|
||||
"index": {
|
||||
"title": "Zen Browser",
|
||||
"hero": {
|
||||
"title": ["welcome", "to", "a", "calmer", "internet"],
|
||||
"description": [
|
||||
"Beautifully designed, privacy-focused, and packed with features.",
|
||||
"We care about your experience, not your data."
|
||||
],
|
||||
"buttons": {
|
||||
"beta": "Beta is now available!",
|
||||
"support": "Support Us ❤️"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"title1": "Productivity",
|
||||
"title2": "at",
|
||||
"title3": "its best",
|
||||
"description": "Zen Browser is packed with features that help you stay productive and focused. Browsers should be tools that help you get things done, not distractions that keep you from your work.",
|
||||
"featureTabs": {
|
||||
"workspaces": {
|
||||
"title": "Workspaces",
|
||||
"description": "Organize your tabs into Workspaces to keep your projects separate and organized, and switch between them with ease."
|
||||
},
|
||||
"compactMode": {
|
||||
"title": "Compact Mode",
|
||||
"description": "Zen's Compact Mode gives you more screen real estate by hiding the tab bar when you don't need it, and showing it when you do."
|
||||
},
|
||||
"glance": {
|
||||
"title": "Glance",
|
||||
"description": "Glance allows you to quickly switch between your most used tabs, without having to scroll through your history."
|
||||
},
|
||||
"splitView": {
|
||||
"title": "Split View",
|
||||
"description": "Split View allows you to view two tabs side by side, making it easier to compare and switch between them."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sponsors": {
|
||||
"title": "Our Sponsors",
|
||||
"description": "We are grateful to our sponsors for their support. They help us to keep the project alive.<br />You can also be part of this journey by <a href=\"/donate\" class=\"zen-link\">donating us directly</a>!",
|
||||
"sponsors": {
|
||||
"tuta": {
|
||||
"name": "Tuta",
|
||||
"url": "https://tuta.com/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"title": ["Our", "Core", "Values"],
|
||||
"description": "We make it not only a priority, but a necessity to ensure that Zen always strikes the right balance between beauty, performance, and privacy. We are committed to making Zen the most beautiful, productive, and privacy-respecting browser out there — without compromising on your experience.",
|
||||
"lists": {
|
||||
"freeAndOpenSource": {
|
||||
"title": "Free and open-source",
|
||||
"description": "Zen is free and open-source software, which means you can use it without any cost and can modify it to suit your needs."
|
||||
},
|
||||
"simpleYetPowerful": {
|
||||
"title": "Simple yet powerful",
|
||||
"description": "Zen is simple to use, but powerful enough to handle your daily tasks."
|
||||
},
|
||||
"privateAndAlwaysUpToDate": {
|
||||
"title": "Private and always up-to-date",
|
||||
"description": "Zen is private and always up-to-date, which means you can use it without any cost and can modify it to suit your needs."
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"community": {
|
||||
"alt": "Community"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mods": {
|
||||
"title": "Zen Mods",
|
||||
"description": "Browse our diverse collection of Zen Mods, community-made plugins and themes for Zen Browser. Discover a theme to match every mood, and a plugin to fulfill every requirement. Start customizing your browser experience today!",
|
||||
"pagination": {
|
||||
"pagination": "{input} of {totalPages} ({totalItems} items)"
|
||||
},
|
||||
"search": "Type to search...",
|
||||
"sort": {
|
||||
"lastCreated": "Last created",
|
||||
"lastUpdated": "Last updated",
|
||||
"perPage": "Per page"
|
||||
},
|
||||
"noResults": "No results found",
|
||||
"noResultsDescription": "Try searching for a different term or check back later.",
|
||||
"slug": {
|
||||
"title": "{name} - Zen Mods",
|
||||
"description": "Learn more about {name} mod available on Zen Browser",
|
||||
"alert": {
|
||||
"description": "You need to have Zen Browser installed to install this theme.",
|
||||
"button": "Download now!"
|
||||
},
|
||||
"createdBy": "Created by <a href={link} class=\"zen-link font-bold\">{author}</a> • <span class=\"font-bold\">v{version}</span>",
|
||||
"creationDate": "Creation date • <b>{createdAt}</b>",
|
||||
"latestUpdate": "Latest update • <b>{updatedAt}</b>",
|
||||
"visitModHomepage": "Visit mod homepage",
|
||||
"installMod": "Install Mod 🎉",
|
||||
"uninstallMod": "Uninstall Mod",
|
||||
"back": "Back"
|
||||
}
|
||||
},
|
||||
"releaseNotes": {
|
||||
"title": "Release notes - Zen Browser",
|
||||
"topSection": {
|
||||
"title": "Release Notes",
|
||||
"description": "Stay up to date with the latest changes to Zen Browser! Since the <a class=\"zen-link\" href=\"#1.0.0-a.1\">first release</a> till <a class=\"zen-link\" href=\"#{latestVersion}\">{latestVersion}</a>, we've been working hard to make Zen Browser the best it can be. Thanks everyone for your feedback! ❤️"
|
||||
},
|
||||
"list": {
|
||||
"support": "Give us some support!",
|
||||
"expandAll": "Expand all",
|
||||
"navigateToVersion": "Navigate to version..."
|
||||
},
|
||||
"backToTop": "Back to the top",
|
||||
"chooseVersion": "Choose version",
|
||||
"components": {
|
||||
"releaseNoteItem": {
|
||||
"twilight": "Twilight",
|
||||
"twilightChanges": "Twilight changes for {version} 🌙",
|
||||
"releaseChanges": "Release notes for {version} 🎉",
|
||||
"firefoxVersion": "Firefox {version}",
|
||||
"githubRelease": "GitHub Release",
|
||||
"workflowRun": "Workflow run",
|
||||
"compareChanges": "Compare changes",
|
||||
"twilightWarning": "Please note that Twilight is a pre-release version of Zen Browser. It may contain bugs and unfinished features.",
|
||||
"reportIssues": " If you encounter any issues, please report them on <a rel=\"noopener noreferrer\" target=\"_blank\" href=\"https://github.com/zen-browser/desktop/issues/\" class=\"zen-link\">the issues page</a>.",
|
||||
"sections": {
|
||||
"fixes": "Fixes",
|
||||
"features": "Features",
|
||||
"themeChanges": "Theme Changes",
|
||||
"breakingChanges": {
|
||||
"title": "Breaking Changes",
|
||||
"description": "View breaking changes on GitHub"
|
||||
}
|
||||
},
|
||||
"learnMore": "Learn more",
|
||||
"viewIssue": "View issue number {issue} on GitHub"
|
||||
}
|
||||
},
|
||||
"slug": {
|
||||
"title": "Release notes",
|
||||
"redirect": "Redirecting to release notes for version {version}..."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "About - Zen Browser",
|
||||
"description": "We are simply a group of developers and designers who care about your experience on the web. We believe that the internet should be a place where you can explore, learn, and connect without worrying about your data being collected.",
|
||||
"littleHelp": "A little help?",
|
||||
"mainTeam": {
|
||||
"title": "Main Team",
|
||||
"description": "This list shows the main team members who are working hard to bring you the best browsing experience.",
|
||||
"members": {
|
||||
"mauro": {
|
||||
"name": "Mauro B.",
|
||||
"description": "Creator, Main Developer",
|
||||
"link": "https://cheff.dev/"
|
||||
},
|
||||
"oscar": {
|
||||
"name": "Oscar Gonzalez",
|
||||
"description": "Site Reliability Engineer (SRE) and code signing.",
|
||||
"link": false
|
||||
},
|
||||
"jan": {
|
||||
"name": "Jan Heres",
|
||||
"description": "Active contributor and helps with MacOS builds",
|
||||
"link": "https://janheres.eu/"
|
||||
},
|
||||
"brhm": {
|
||||
"name": "BrhmDev",
|
||||
"description": "Active contributor with great contributions",
|
||||
"link": "https://github.com/BrhmDev"
|
||||
},
|
||||
"canoa": {
|
||||
"name": "Canoa",
|
||||
"description": "Active contributor, and very active in issue handling and website management",
|
||||
"link": "https://thatcanoa.org/"
|
||||
},
|
||||
"adam": {
|
||||
"name": "Adam",
|
||||
"description": "Branding and design",
|
||||
"link": "https://cybrneon.xyz/"
|
||||
},
|
||||
"kristijanribaric": {
|
||||
"name": "Kristijan Ribaric",
|
||||
"description": "Active contributor",
|
||||
"link": "https://github.com/kristijanribaric"
|
||||
},
|
||||
"n7itro": {
|
||||
"name": "n7itro",
|
||||
"description": "Active contributor and release notes writer",
|
||||
"link": "https://github.com/n7itro"
|
||||
},
|
||||
"bryan": {
|
||||
"name": "Bryan Galdámez",
|
||||
"description": "Huge contributor on theme functionalities",
|
||||
"link": "https://josuegalre.netlify.app/"
|
||||
},
|
||||
"jafeth": {
|
||||
"name": "Jafeth Garro",
|
||||
"description": "Documentation writer",
|
||||
"link": "https://iamjafeth.com/"
|
||||
},
|
||||
"larvey": {
|
||||
"name": "Larvey",
|
||||
"description": "AUR maintainer",
|
||||
"link": "https://github.com/LarveyOfficial/"
|
||||
},
|
||||
"daniel": {
|
||||
"name": "Daniel García",
|
||||
"description": "MacOS certificate and app notarization maintainer",
|
||||
"link": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"contributors": {
|
||||
"title": "Contributors",
|
||||
"description": "This list shows the contributors who have helped us to make Zen Browser the best it can be."
|
||||
}
|
||||
},
|
||||
"donate": {
|
||||
"title": "Donate - Zen Browser",
|
||||
"description": "We are a small team of developers working hard to bring you the best browsing experience. If you like what we do, please consider supporting us.",
|
||||
"patreon": {
|
||||
"title": "Patreon",
|
||||
"description": "Patreon allows you to support us with a monthly donation. You can choose the level of support that works best for you."
|
||||
},
|
||||
"koFi": {
|
||||
"title": "Ko-fi",
|
||||
"description": "Ko-fi allows you to support us with a one-time donation. You can choose the amount that works best for you. Monthly donations are also available.",
|
||||
"button": "Go to Ko-fi"
|
||||
}
|
||||
},
|
||||
"download": {
|
||||
"title": "Download - Zen Browser",
|
||||
"description": "Download Zen Browser for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification.",
|
||||
"twilightInfo": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates.",
|
||||
"alertInfo": {
|
||||
"description": "<strong class='font-medium text-zen-blue'>Twilight Mode:</strong> You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
|
||||
},
|
||||
"platformSelector": {
|
||||
"title": "Platform Selector",
|
||||
"description": "Select your platform to download Zen Browser."
|
||||
},
|
||||
"additionalResources": {
|
||||
"title": "Additional Resources",
|
||||
"sourceCode": {
|
||||
"title": "Source Code",
|
||||
"description": "Explore Zen Browser's source code on GitHub. Contribute to the project or build your own version."
|
||||
},
|
||||
"documentation": {
|
||||
"title": "Documentation",
|
||||
"description": "Access comprehensive documentation, guides, and tutorials for Zen Browser."
|
||||
}
|
||||
},
|
||||
"securityNotice": {
|
||||
"title": "Verified & Secure Downloads",
|
||||
"description": "All Zen downloads are signed and verified for your security. We recommend downloading directly from our official website or GitHub repository. If your download seems broken or gets flagged by your antivirus, please <a href='https://github.com/zen-browser/desktop/issues/new/choose' class='zen-link ml-1'>report it to us</a>."
|
||||
},
|
||||
"platformNames": {
|
||||
"mac": "macOS",
|
||||
"windows": "Windows",
|
||||
"linux": "Linux",
|
||||
"macDownload": "MacOS Download",
|
||||
"windowsDownload": "Windows Download",
|
||||
"linuxDownload": "Linux Download"
|
||||
},
|
||||
"platformDescriptions": {
|
||||
"mac": "Works on both new Apple (M-Series) and older Intel Macs.<br />Requires macOS 11.0 or later.",
|
||||
"windows": "Works on Windows 10 and Windows 11.<br />Not sure which version to get? Most people should choose the 64-bit installer.",
|
||||
"linux": "Works with many Linux versions.<br />Pick the download that matches your system."
|
||||
}
|
||||
},
|
||||
"privacyPolicy": {
|
||||
"title": "Privacy Policy",
|
||||
"lastUpdated": "Last updated: 2025-02-5",
|
||||
"sections": {
|
||||
"introduction": {
|
||||
"title": "Introduction",
|
||||
"body": "Welcome to Zen Browser! Your privacy is our priority. This Privacy Policy outlines the types of personal information we collect, how we use it, and the steps we take to protect your data when you use Zen Browser.",
|
||||
"summary": "We don't sell data - We don't collect data - We don't track you"
|
||||
},
|
||||
"noCollect": {
|
||||
"title": "1. Information We Do Not Collect",
|
||||
"body": "Zen Browser is designed with privacy in mind. We do not collect, store, or share any of your personal data. Here's what that means:"
|
||||
},
|
||||
"noTelemetry": {
|
||||
"title": "1.1. No Telemetry",
|
||||
"body": "We do not collect any telemetry data or crash reports.",
|
||||
"body2": "Zen Browser has stripped out telemetry built into Mozilla Firefox. We have removed all telemetry data collection and crash reports."
|
||||
},
|
||||
"noPersonalData": {
|
||||
"title": "1.2. No Personal Data Collection",
|
||||
"body": "Zen Browser does not collect any personal information such as your IP address, browsing history, search queries, or form data."
|
||||
},
|
||||
"noThirdParty": {
|
||||
"title": "1.3. No Third-Party Tracking",
|
||||
"body": "We do not allow third-party trackers or analytics tools to operate within Zen Browser. Your browsing activity remains entirely private and is not shared with any third party. Mozilla is not considered a third party as it is the base of Zen Browser."
|
||||
},
|
||||
"externalConnections": {
|
||||
"title": "1.4. External connections made at startup",
|
||||
"body": "Zen Browser may make external connections at startup to check for updates and ensure the browser is up to date on plugins, addons, check for connectivity and Geolocation/push notifications services in order to comply with web standards. We, at Zen, do not collect any data from these connections, but they may be logged by third-party services or websites you visit. These connections are necessary for the proper functioning of the browser and are not used for tracking or profiling purposes. They can be disabled through the browser flags (about:config)."
|
||||
},
|
||||
"localStorage": {
|
||||
"title": "2. Information Stored Locally on Your Device"
|
||||
},
|
||||
"browsingData": {
|
||||
"title": "2.1. Browsing Data",
|
||||
"body": "Zen Browser stores certain data locally on your device to enhance your browsing experience. This includes:"
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"body": "Cookies are stored locally on your device and are not shared with Zen Browser or any third party. You have full control over the management of cookies through the browser's settings."
|
||||
},
|
||||
"cache": {
|
||||
"title": "Cache and Temporary Files",
|
||||
"body": "Zen Browser may store cache files and other temporary data locally to improve performance. These files can be cleared at any time through the browser's settings."
|
||||
},
|
||||
"settings": {
|
||||
"title": "2.2. Settings and Preferences",
|
||||
"body": "Any customizations, settings, and preferences you make within Zen Browser are stored locally on your device. We do not have access to or control over this data."
|
||||
},
|
||||
"sync": {
|
||||
"title": "3. Sync Feature",
|
||||
"body": "Zen Browser offers a \"Sync\" feature, which is implemented using Mozilla Firefox's Sync feature. This feature allows you to synchronize your bookmarks, history, passwords, and other data across multiple devices. For this feature to work, your data is encrypted and stored on Mozilla's servers and is treated in accordance with their Privacy Policy. We, at Zen, cannot view any of this data.",
|
||||
"link1": "Mozilla Firefox Sync",
|
||||
"link2": "This is how we store your passwords"
|
||||
},
|
||||
"addons": {
|
||||
"title": "4. Add-ons and \"Mods\"",
|
||||
"body": "You can install Add-ons from addons.mozilla.org. Zen Browser periodically checks for updates to these Add-ons.\nYou can also install \"Mods\" from zen-browser.app/mods. These Mods are hosted by our services and follow the same privacy policy our website. We do not collect any data from these Mods, they are purely static content that is downloaded to your device."
|
||||
},
|
||||
"security": {
|
||||
"title": "5. Data Security",
|
||||
"body": "Although Zen Browser does not collect your data, we are committed to protecting the information that is stored locally on your device and, if you use the Sync feature, the encrypted data stored on Mozilla's servers. We recommend that you use secure passwords, enable device encryption, and regularly update your software to ensure your data remains safe.",
|
||||
"note": "Note that most of the security measures are taken care by Mozilla Firefox."
|
||||
},
|
||||
"control": {
|
||||
"title": "6. Your Control",
|
||||
"deletionTitle": "6.1. Data Deletion",
|
||||
"deletionBody": "You have full control over all data stored locally on your device by Zen Browser. You can clear your browsing data, cookies, and cache at any time using the browser's settings."
|
||||
},
|
||||
"website": {
|
||||
"title": "7. Our Website and Services",
|
||||
"body": "Zen Browser's website and services do not use any third-party analytics, tracking, or CDN services. We do not collect any personal information from users visiting our website. The website is hosted on Cloudflare but with analytics and tracking disabled, Cloudflare may collect some analytics data from HTTP requests in order to provide security and performance improvements. However, this data is not linked to any personal information and is not used for tracking purposes.",
|
||||
"externalLinksTitle": "7.1. External links",
|
||||
"externalLinksBody": "Zen Browser may contain links to external websites or services that are not owned or operated by us. We are not responsible for the content or privacy practices of these sites. We recommend that you review the privacy policies of these sites before providing them with any personal information."
|
||||
},
|
||||
"changes": {
|
||||
"title": "8. Changes to This Privacy Policy",
|
||||
"body": "We may update this Privacy Policy from time to time to reflect changes in our practices or legal requirements. We will notify you of any significant changes by updating the effective date at the top of this policy. Continued use of Zen Browser after such changes constitutes your acceptance of the new terms."
|
||||
},
|
||||
"otherTelemetry": {
|
||||
"title": "9. Other telemetry done by Mozilla Firefox",
|
||||
"body": "We try to disable all telemetry data collection in Zen Browser. But, we may have missed some. Check the below links for more information.",
|
||||
"firefoxPrivacyNotice": "Firefox Privacy Notice",
|
||||
"forMoreInformation": "for more information."
|
||||
},
|
||||
"contact": {
|
||||
"title": "10. Contact Us",
|
||||
"body": "If you have any questions or concerns about this Privacy Policy or Zen Browser, please contact us at:",
|
||||
"discord": "Discord: ",
|
||||
"discordLink": "Zen Browser's Discord",
|
||||
"github": "GitHub: ",
|
||||
"githubLink": "Organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": ["Welcome", "to", "Zen!"]
|
||||
},
|
||||
"whatsNew": {
|
||||
"title": "What's New in {latestVersion.version}!",
|
||||
"reportIssue": "Report an issue",
|
||||
"joinDiscord": "Join our Discord",
|
||||
"readFullReleaseNotes": "Read the full release notes"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page Not Found",
|
||||
"description": "Sorry, the page you are looking for does not exist or has been moved.",
|
||||
"button": "Go Home"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"index": {
|
||||
"title": "Zen Browser",
|
||||
"description": "Beautifully designed, privacy-focused, and packed with features."
|
||||
},
|
||||
"mods": {
|
||||
"title": "Zen Mods",
|
||||
"description": "Browse our diverse collection of Zen Mods, community-made plugins and themes for Zen Browser. Discover a theme to match every mood, and a plugin to fulfill every requirement. Start customizing your browser experience today!"
|
||||
},
|
||||
"releaseNotes": {
|
||||
"title": "Release notes - Zen",
|
||||
"description": "Stay up to date with the latest changes to Zen Browser! Since the first release till {latestVersion}, we've been working hard to make Zen Browser the best it can be. Thanks everyone for your feedback! ❤️"
|
||||
},
|
||||
"about": {
|
||||
"title": "About - Zen",
|
||||
"description": "We are simply a group of developers and designers who care about your experience on the web. We believe that the internet should be a place where you can explore, learn, and connect without worrying about your data being collected."
|
||||
},
|
||||
"donate": {
|
||||
"title": "Donate - Zen",
|
||||
"description": "We are a small team of developers working hard to bring you the best browsing experience. If you like what we do, please consider supporting us."
|
||||
},
|
||||
"download": {
|
||||
"title": "Download - Zen",
|
||||
"description": "Download Zen Browser for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification."
|
||||
},
|
||||
"privacyPolicy": {
|
||||
"title": "Privacy Policy - Zen",
|
||||
"description": "Your privacy is our priority. This Privacy Policy outlines the types of personal information we collect, how we use it, and the steps we take to protect your data when you use Zen Browser."
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Welcome!",
|
||||
"description": "Welcome to Zen!"
|
||||
},
|
||||
"whatsNew": {
|
||||
"title": "What's New in {latestVersion.version}!"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"footer": {
|
||||
"title": "Zen Browser",
|
||||
"description": "Beautifully designed, privacy-focused, and packed with features. We care about your experience, not your data.",
|
||||
"download": "Download",
|
||||
"followUs": "Follow Us",
|
||||
"aboutUs": "About Us",
|
||||
"teamAndContributors": "Team & Contributors",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"getStarted": "Get Started",
|
||||
"documentation": "Documentation",
|
||||
"zenMods": "Zen Mods",
|
||||
"releaseNotes": "Release Notes",
|
||||
"getHelp": "Get Help",
|
||||
"discord": "Discord",
|
||||
"uptimeStatus": "Uptime Status",
|
||||
"reportAnIssue": "Report an Issue",
|
||||
"twilight": "Twilight",
|
||||
"madeWith": "Made with <span aria-label='love'>❤️</span> by the <a href='{link}' class='zen-link inline-block font-bold'>Zen Team</a>"
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Zen Browser",
|
||||
"menu": {
|
||||
"gettingStarted": "Getting Started",
|
||||
"usefulLinks": "Useful Links",
|
||||
"mods": "Mods",
|
||||
"download": "Download",
|
||||
"discord": "Discord",
|
||||
"releaseNotes": "Release Notes",
|
||||
"zenMods": "Zen Mods",
|
||||
"tryZenMods": "Try Zen Mods",
|
||||
"zenModsDesc": "Customize your browsing experience with Zen Mods.",
|
||||
"releaseNotesDesc": "Stay up to date with the latest features and improvements.",
|
||||
"discordDesc": "Join our community on Discord to chat with other Zen users!",
|
||||
"donate": "Donate ❤️",
|
||||
"donateDesc": "Support the development of Zen Browser with a donation.",
|
||||
"aboutUs": "About Us 🌟",
|
||||
"aboutUsDesc": "Learn more about the team behind Zen Browser.",
|
||||
"documentation": "Documentation",
|
||||
"documentationDesc": "Learn how to use Zen Browser with our documentation.",
|
||||
"github": "GitHub",
|
||||
"githubDesc": "Contribute to the development of Zen Browser on GitHub.",
|
||||
"menu": "Menu"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import NotFound from "./[...locale]/404.astro";
|
||||
import NotFound from './[...locale]/404.astro'
|
||||
---
|
||||
|
||||
<NotFound />
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
---
|
||||
import Button from "~/components/Button.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import Title from "~/components/Title.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import Button from '~/components/Button.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import Title from '~/components/Title.astro'
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||
export { getStaticPaths } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const getLocalePath = getPath(locale);
|
||||
const locale = getLocale(Astro)
|
||||
const getLocalePath = getPath(locale)
|
||||
const {
|
||||
routes: { notFound },
|
||||
} = getUI(locale);
|
||||
routes: { notFound },
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout title={notFound.title}>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
---
|
||||
import { ArrowRight } from "lucide-astro";
|
||||
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 { ArrowRight } from 'lucide-astro'
|
||||
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: { donate },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { donate },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout title={layout.donate.title} description={layout.donate.description}>
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
---
|
||||
import Description from "~/components/Description.astro";
|
||||
import DownloadScript from "~/components/download/DownloadScript.astro";
|
||||
import PlatformDownload from "~/components/download/PlatformDownload.astro";
|
||||
import { getReleasesWithChecksums } from "~/components/download/release-data.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getChecksums } from "~/utils/githubChecksums";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
import Description from '~/components/Description.astro'
|
||||
import DownloadScript from '~/components/download/DownloadScript.astro'
|
||||
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
||||
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
import { getChecksums } from '~/utils/githubChecksums'
|
||||
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 { ExternalLink, Lock } from "lucide-astro";
|
||||
faApple,
|
||||
faGithub,
|
||||
faLinux,
|
||||
faWindows,
|
||||
} from '@fortawesome/free-brands-svg-icons'
|
||||
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 {
|
||||
routes: { download },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { download },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
|
||||
library.add(faWindows, faLinux, faApple, faGithub);
|
||||
const windowsIcon = icon({ prefix: "fab", iconName: "windows" });
|
||||
const linuxIcon = icon({ prefix: "fab", iconName: "linux" });
|
||||
const appleIcon = icon({ prefix: "fab", iconName: "apple" });
|
||||
const githubIcon = icon({ prefix: "fab", iconName: "github" });
|
||||
library.add(faWindows, faLinux, faApple, faGithub)
|
||||
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
||||
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
||||
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
||||
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
|
||||
|
||||
const checksums = await getChecksums();
|
||||
const releases = getReleasesWithChecksums(checksums);
|
||||
const checksums = await getChecksums()
|
||||
const releases = getReleasesWithChecksums(checksums)
|
||||
|
||||
const platformNames = download.platformNames;
|
||||
const platformDescriptions = download.platformDescriptions;
|
||||
const platformNames = download.platformNames
|
||||
const platformDescriptions = download.platformDescriptions
|
||||
---
|
||||
|
||||
<DownloadScript />
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import rss, { type RSSOptions } from "@astrojs/rss";
|
||||
import { releaseNotes } from "~/release-notes";
|
||||
import type { ReleaseNote } from "~/release-notes";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import rss, { type RSSOptions } from '@astrojs/rss'
|
||||
import { releaseNotes } from '~/release-notes'
|
||||
import type { ReleaseNote } from '~/release-notes'
|
||||
export { getStaticPaths } from '~/utils/i18n'
|
||||
|
||||
/** 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.
|
||||
* @returns The RSS feed for the Zen Browser release notes.
|
||||
*/
|
||||
export function GET(context: any) {
|
||||
// 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();
|
||||
// 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 rssData: RSSOptions = {
|
||||
title: "Zen Browser Release Notes",
|
||||
description: "Release Notes for the Zen Browser",
|
||||
site: context.url,
|
||||
items: [],
|
||||
customData: `
|
||||
const rssData: RSSOptions = {
|
||||
title: 'Zen Browser Release Notes',
|
||||
description: 'Release Notes for the Zen Browser',
|
||||
site: context.url,
|
||||
items: [],
|
||||
customData: `
|
||||
<language>en</language>
|
||||
<link>https://www.zen-browser.app/release-notes</link>
|
||||
<copyright>Zen Browser © ${new Date().getFullYear()} - Made with ❤️ by the Zen team.</copyright>
|
||||
|
@ -33,19 +33,19 @@ export function GET(context: any) {
|
|||
<link>https://www.zen-browser.app</link>
|
||||
</image>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
||||
rssData.items.push({
|
||||
title: `Release notes for version ${releaseNote.version}`,
|
||||
link: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
||||
pubDate: formatRssDate(releaseNote.date as string),
|
||||
description: releaseNote.extra,
|
||||
content: formatReleaseNote(releaseNote),
|
||||
});
|
||||
}
|
||||
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
||||
rssData.items.push({
|
||||
title: `Release notes for version ${releaseNote.version}`,
|
||||
link: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
||||
pubDate: formatRssDate(releaseNote.date as string),
|
||||
description: releaseNote.extra,
|
||||
content: formatReleaseNote(releaseNote),
|
||||
})
|
||||
}
|
||||
|
||||
return rss(rssData);
|
||||
return rss(rssData)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,15 +56,15 @@ export function GET(context: any) {
|
|||
* @returns The passed in date string as a Date object.
|
||||
*/
|
||||
function formatRssDate(dateStr: string) {
|
||||
const splitDate = dateStr.split("/");
|
||||
if (splitDate.length !== 3) {
|
||||
throw new Error("Invalid date format");
|
||||
}
|
||||
const splitDate = dateStr.split('/')
|
||||
if (splitDate.length !== 3) {
|
||||
throw new Error('Invalid date format')
|
||||
}
|
||||
|
||||
const day = Number(splitDate[0]);
|
||||
const month = Number(splitDate[1]) - 1;
|
||||
const year = Number(splitDate[2]);
|
||||
return new Date(year, month, day);
|
||||
const day = Number(splitDate[0])
|
||||
const month = Number(splitDate[1]) - 1
|
||||
const year = Number(splitDate[2])
|
||||
return new Date(year, month, day)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,102 +73,102 @@ function formatRssDate(dateStr: string) {
|
|||
* @returns The formatted release note as a HTML string.
|
||||
*/
|
||||
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>.
|
||||
Thanks everyone for your feedback! ❤️
|
||||
</p>`;
|
||||
</p>`
|
||||
|
||||
if (releaseNote.image) {
|
||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||
if (releaseNote.image) {
|
||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||
alt="Release Image for version ${releaseNote.version}"
|
||||
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
||||
/>`;
|
||||
}
|
||||
/>`
|
||||
}
|
||||
|
||||
if (releaseNote.extra) {
|
||||
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`;
|
||||
}
|
||||
if (releaseNote.extra) {
|
||||
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("🖌 Theme Changes", releaseNote.themeChanges);
|
||||
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
|
||||
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)
|
||||
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
|
||||
function addReleaseNoteSection(title: string, items?: string[]): string {
|
||||
if (!items) {
|
||||
return "";
|
||||
}
|
||||
if (!items) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let content = `<h2>${title}</h2>`;
|
||||
content += `<ul>`;
|
||||
for (const item of items) {
|
||||
if (item && item.length > 0) {
|
||||
content += `<li>${item}</li>`;
|
||||
}
|
||||
}
|
||||
content += `</ul>`;
|
||||
return content;
|
||||
let content = `<h2>${title}</h2>`
|
||||
content += `<ul>`
|
||||
for (const item of items) {
|
||||
if (item && item.length > 0) {
|
||||
content += `<li>${item}</li>`
|
||||
}
|
||||
}
|
||||
content += `</ul>`
|
||||
return content
|
||||
}
|
||||
|
||||
function fixToReleaseNote(
|
||||
fix?: Exclude<ReleaseNote["fixes"], undefined>[number],
|
||||
fix?: Exclude<ReleaseNote['fixes'], undefined>[number],
|
||||
) {
|
||||
if (typeof fix === "string") {
|
||||
return fix;
|
||||
}
|
||||
if (typeof fix === 'string') {
|
||||
return fix
|
||||
}
|
||||
|
||||
if (!fix || !fix.description || fix.description.length === 0) {
|
||||
return "";
|
||||
}
|
||||
if (!fix || !fix.description || fix.description.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let note = fix.description;
|
||||
if (fix.issue) {
|
||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
|
||||
}
|
||||
return note;
|
||||
let note = fix.description
|
||||
if (fix.issue) {
|
||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`
|
||||
}
|
||||
return note
|
||||
}
|
||||
|
||||
function breakingChangeToReleaseNote(
|
||||
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number],
|
||||
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number],
|
||||
) {
|
||||
if (typeof breakingChange === "string") {
|
||||
return breakingChange;
|
||||
}
|
||||
if (typeof breakingChange === 'string') {
|
||||
return breakingChange
|
||||
}
|
||||
|
||||
if (
|
||||
!breakingChange ||
|
||||
!breakingChange.description ||
|
||||
breakingChange.description.length === 0
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
if (
|
||||
!breakingChange ||
|
||||
!breakingChange.description ||
|
||||
breakingChange.description.length === 0
|
||||
) {
|
||||
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) {
|
||||
date ??= new Date();
|
||||
date ??= new Date()
|
||||
|
||||
const pieces = date.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 pieces = date.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,
|
||||
]
|
||||
|
||||
return parts.join(" ");
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
import Community from "~/components/Community.astro";
|
||||
import Features from "~/components/Features.astro";
|
||||
import Hero from "~/components/Hero.astro";
|
||||
import Sponsors from "~/components/Sponsors.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import Community from '~/components/Community.astro'
|
||||
import Features from '~/components/Features.astro'
|
||||
import Hero from '~/components/Hero.astro'
|
||||
import Sponsors from '~/components/Sponsors.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 { layout } = getUI(locale);
|
||||
const { layout } = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
---
|
||||
import { ArrowRight, Info } from "lucide-astro";
|
||||
import BackButton from "~/components/BackButton.astro";
|
||||
import Button from "~/components/Button.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods";
|
||||
import { getUI } from "~/utils/i18n";
|
||||
import { getLocale, getOtherLocales } from "~/utils/i18n";
|
||||
import { ArrowRight, Info } from 'lucide-astro'
|
||||
import BackButton from '~/components/BackButton.astro'
|
||||
import Button from '~/components/Button.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
|
||||
import { getUI } from '~/utils/i18n'
|
||||
import { getLocale, getOtherLocales } from '~/utils/i18n'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const mods = await getAllMods();
|
||||
return mods.flatMap((mod) => [
|
||||
...getOtherLocales().map((locale) => ({
|
||||
params: {
|
||||
slug: mod.id,
|
||||
locale: locale,
|
||||
},
|
||||
props: {
|
||||
...mod,
|
||||
locale: locale,
|
||||
},
|
||||
})),
|
||||
{
|
||||
params: {
|
||||
slug: mod.id,
|
||||
locale: undefined,
|
||||
},
|
||||
props: {
|
||||
...mod,
|
||||
locale: undefined,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const mods = await getAllMods()
|
||||
return mods.flatMap((mod) => [
|
||||
...getOtherLocales().map((locale) => ({
|
||||
params: {
|
||||
slug: mod.id,
|
||||
locale: locale,
|
||||
},
|
||||
props: {
|
||||
...mod,
|
||||
locale: locale,
|
||||
},
|
||||
})),
|
||||
{
|
||||
params: {
|
||||
slug: mod.id,
|
||||
locale: undefined,
|
||||
},
|
||||
props: {
|
||||
...mod,
|
||||
locale: undefined,
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
|
||||
|
||||
const mod = Astro.props;
|
||||
const mod = Astro.props
|
||||
|
||||
const dates = {
|
||||
createdAt: getLocalizedDate(mod.createdAt),
|
||||
updatedAt: getLocalizedDate(mod.updatedAt),
|
||||
};
|
||||
createdAt: getLocalizedDate(mod.createdAt),
|
||||
updatedAt: getLocalizedDate(mod.updatedAt),
|
||||
}
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: {
|
||||
mods: { slug },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
mods: { slug },
|
||||
},
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
@ -104,7 +104,7 @@ const {
|
|||
<p
|
||||
set:html={slug.latestUpdate.replace(
|
||||
'{updatedAt}',
|
||||
dates.updatedAt
|
||||
dates.updatedAt,
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
import Description from "~/components/Description.astro";
|
||||
import ModsList from "~/components/ModsList";
|
||||
import { CONSTANT } from "~/constants";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getAllMods } from "~/mods";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import Description from '~/components/Description.astro'
|
||||
import ModsList from '~/components/ModsList'
|
||||
import { CONSTANT } from '~/constants'
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
import { getAllMods } from '~/mods'
|
||||
import { getLocale, getUI } from '~/utils/i18n'
|
||||
export { getStaticPaths } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: { mods },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { mods },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
|
||||
const allMods = (await getAllMods()) || [];
|
||||
const allMods = (await getAllMods()) || []
|
||||
---
|
||||
|
||||
<Layout title={layout.mods.title}>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
import Title from "~/components/Title.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import Title from '~/components/Title.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: { privacyPolicy },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { privacyPolicy },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
---
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { releaseNotes } from "~/release-notes";
|
||||
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n";
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
import { releaseNotes } from '~/release-notes'
|
||||
import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: {
|
||||
releaseNotes: { slug },
|
||||
},
|
||||
} = getUI(locale);
|
||||
routes: {
|
||||
releaseNotes: { slug },
|
||||
},
|
||||
} = getUI(locale)
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const i18nPaths = getI18nPaths();
|
||||
const i18nPaths = getI18nPaths()
|
||||
|
||||
return i18nPaths.flatMap(({ params: { locale } }) => [
|
||||
...releaseNotes.map((release: any) => ({
|
||||
params: { slug: release.version, locale },
|
||||
props: { ...release },
|
||||
})),
|
||||
{
|
||||
params: { slug: "latest", locale },
|
||||
props: { ...releaseNotes[0] },
|
||||
},
|
||||
]);
|
||||
return i18nPaths.flatMap(({ params: { locale } }) => [
|
||||
...releaseNotes.map((release: any) => ({
|
||||
params: { slug: release.version, locale },
|
||||
props: { ...release },
|
||||
})),
|
||||
{
|
||||
params: { slug: 'latest', locale },
|
||||
props: { ...releaseNotes[0] },
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const release = Astro.props;
|
||||
const release = Astro.props
|
||||
---
|
||||
|
||||
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
---
|
||||
import { Modal, ModalBody, ModalHeader } from "free-astro-components";
|
||||
import { ArrowUp } from "lucide-astro";
|
||||
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 { Modal, ModalBody, ModalHeader } from 'free-astro-components'
|
||||
import { ArrowUp } from 'lucide-astro'
|
||||
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 { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
releaseNotes as releaseNotesData,
|
||||
releaseNotesTwilight,
|
||||
} from '~/release-notes'
|
||||
import { getLocale, getUI } from '~/utils/i18n'
|
||||
export { getStaticPaths } from '~/utils/i18n'
|
||||
|
||||
const locale = getLocale(Astro);
|
||||
const locale = getLocale(Astro)
|
||||
|
||||
const {
|
||||
routes: { releaseNotes },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { releaseNotes },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout title={layout.releaseNotes.title}>
|
||||
|
@ -33,7 +33,7 @@ const {
|
|||
class="text-base opacity-55"
|
||||
set:html={releaseNotes.topSection.description.replaceAll(
|
||||
'{latestVersion}',
|
||||
releaseNotesData[0].version
|
||||
releaseNotesData[0].version,
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
import Features from "~/components/Features.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import Features from '~/components/Features.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: { welcome },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { welcome },
|
||||
layout,
|
||||
} = getUI(locale)
|
||||
---
|
||||
|
||||
<Layout title={layout.welcome.title} description={layout.welcome.description}>
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
---
|
||||
import { ArrowRight } from "lucide-astro";
|
||||
import Button from "~/components/Button.astro";
|
||||
import Description from "~/components/Description.astro";
|
||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
||||
import Layout from "~/layouts/Layout.astro";
|
||||
import { ArrowRight } from 'lucide-astro'
|
||||
import Button from '~/components/Button.astro'
|
||||
import Description from '~/components/Description.astro'
|
||||
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||
import Layout from '~/layouts/Layout.astro'
|
||||
|
||||
import whatsNewVideo from "~/assets/whats-new.mp4";
|
||||
import Video from "~/components/Video.astro";
|
||||
import { releaseNotes } from "~/release-notes";
|
||||
import whatsNewText from "~/release-notes/whats-new.json";
|
||||
import { getLocale, getUI } from "~/utils/i18n";
|
||||
export { getStaticPaths } from "~/utils/i18n";
|
||||
import whatsNewVideo from '~/assets/whats-new.mp4'
|
||||
import Video from '~/components/Video.astro'
|
||||
import { releaseNotes } from '~/release-notes'
|
||||
import whatsNewText from '~/release-notes/whats-new.json'
|
||||
import { getLocale, getUI } 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 {
|
||||
routes: { whatsNew },
|
||||
layout,
|
||||
} = getUI(locale);
|
||||
routes: { whatsNew },
|
||||
layout,
|
||||
} = 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
|
||||
latestVersion.version.split('.').length > 2 &&
|
||||
whatsNewText[1] !== latestVersion.version
|
||||
) {
|
||||
return Astro.redirect(`/release-notes#${latestVersion.version}`);
|
||||
return Astro.redirect(`/release-notes#${latestVersion.version}`)
|
||||
}
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={layout.whatsNew.title.replace(
|
||||
'{latestVersion.version}',
|
||||
latestVersion.version
|
||||
latestVersion.version,
|
||||
)}
|
||||
>
|
||||
<main
|
||||
|
@ -45,7 +45,7 @@ if (
|
|||
>{
|
||||
whatsNew.title.replace(
|
||||
'{latestVersion.version}',
|
||||
latestVersion.version
|
||||
latestVersion.version,
|
||||
)
|
||||
}</Description
|
||||
>
|
||||
|
|
|
@ -3,25 +3,29 @@
|
|||
* 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', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'User-Agent': 'zen-browser-checksum-fetcher',
|
||||
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);
|
||||
const data = await res.json();
|
||||
const body = data.body as string;
|
||||
)
|
||||
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> = {};
|
||||
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 => {
|
||||
const [hash, filename] = line.trim().split(/\s+/, 2);
|
||||
if (hash && filename) checksums[filename] = hash;
|
||||
});
|
||||
match[1].split('\n').forEach((line) => {
|
||||
const [hash, filename] = line.trim().split(/\s+/, 2)
|
||||
if (hash && filename) checksums[filename] = hash
|
||||
})
|
||||
}
|
||||
return checksums;
|
||||
}
|
||||
return checksums
|
||||
}
|
||||
|
|
|
@ -1,85 +1,85 @@
|
|||
import type { GetStaticPaths } from "astro";
|
||||
import { CONSTANT } from "~/constants";
|
||||
import UI_EN from "~/i18n/en/translation.json";
|
||||
import type { GetStaticPaths } from 'astro'
|
||||
import { CONSTANT } from '~/constants'
|
||||
import UI_EN from '~/i18n/en/translation.json'
|
||||
|
||||
export type Locale = (typeof locales)[number];
|
||||
export type Locale = (typeof locales)[number]
|
||||
|
||||
export const getPath = (locale?: Locale) => (path: string) => {
|
||||
if (locale && !path.startsWith(`/${locale}`)) {
|
||||
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
if (locale && !path.startsWith(`/${locale}`)) {
|
||||
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
export const getLocale = (Astro: any) => {
|
||||
if (Astro.params.locale) {
|
||||
return Astro.params.locale as Locale;
|
||||
}
|
||||
};
|
||||
if (Astro.params.locale) {
|
||||
return Astro.params.locale as Locale
|
||||
}
|
||||
}
|
||||
|
||||
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value);
|
||||
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value)
|
||||
|
||||
const otherLocales = CONSTANT.I18N.LOCALES.filter(
|
||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
||||
);
|
||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
||||
)
|
||||
|
||||
export const getOtherLocales = () => otherLocales;
|
||||
export const getOtherLocales = () => otherLocales
|
||||
|
||||
export type UI = typeof UI_EN;
|
||||
export type UI = typeof UI_EN
|
||||
|
||||
export const ui = { en: UI_EN };
|
||||
export const ui = { en: UI_EN }
|
||||
|
||||
export const getUI = (locale?: Locale | string): UI => {
|
||||
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];
|
||||
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)
|
||||
return (overrideObj ?? defaultObj) as T;
|
||||
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],
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const key in overrideObj) {
|
||||
if (!(key in defaultObj)) {
|
||||
result[key] = (overrideObj as any)[key];
|
||||
}
|
||||
}
|
||||
return result as T;
|
||||
}
|
||||
function deepMerge<T>(defaultObj: T, overrideObj: Partial<T>): T {
|
||||
if (typeof defaultObj !== 'object' || defaultObj === null)
|
||||
return (overrideObj ?? defaultObj) as T
|
||||
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],
|
||||
)
|
||||
}
|
||||
}
|
||||
for (const key in overrideObj) {
|
||||
if (!(key in defaultObj)) {
|
||||
result[key] = (overrideObj as any)[key]
|
||||
}
|
||||
}
|
||||
return result as T
|
||||
}
|
||||
|
||||
return deepMerge<UI>(defaultUI, localeUI);
|
||||
};
|
||||
return deepMerge<UI>(defaultUI, localeUI)
|
||||
}
|
||||
|
||||
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 }) => ({
|
||||
params: { locale: value },
|
||||
props: {
|
||||
locale: value,
|
||||
},
|
||||
})),
|
||||
];
|
||||
}) satisfies GetStaticPaths;
|
||||
return [
|
||||
{
|
||||
params: { locale: undefined },
|
||||
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
|
||||
},
|
||||
...CONSTANT.I18N.LOCALES.filter(
|
||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
||||
).map(({ value }) => ({
|
||||
params: { locale: value },
|
||||
props: {
|
||||
locale: value,
|
||||
},
|
||||
})),
|
||||
]
|
||||
}) satisfies GetStaticPaths
|
||||
|
||||
export const getLocales = () => {
|
||||
return [...locales, ...otherLocales];
|
||||
};
|
||||
return [...locales, ...otherLocales]
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue