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
|
// @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
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [tailwind(), preact({ compat: true }), sitemap({})],
|
integrations: [tailwind(), preact({ compat: true }), sitemap()],
|
||||||
site: "https://zen-browser.app",
|
site: 'https://zen-browser.app',
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "en",
|
defaultLocale: 'en',
|
||||||
locales: ["en"],
|
locales: ['en'],
|
||||||
routing: {
|
routing: {
|
||||||
fallbackType: "rewrite",
|
fallbackType: 'rewrite',
|
||||||
prefixDefaultLocale: false,
|
prefixDefaultLocale: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
import { ArrowLeft } from "lucide-astro";
|
import { ArrowLeft } from 'lucide-astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
mods: { slug },
|
mods: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath } from "~/utils/i18n";
|
import { getLocale, getPath } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
class: className,
|
class: className,
|
||||||
isPrimary,
|
isPrimary,
|
||||||
isAlert,
|
isAlert,
|
||||||
isBordered,
|
isBordered,
|
||||||
href,
|
href,
|
||||||
id,
|
id,
|
||||||
extra,
|
extra,
|
||||||
} = Astro.props;
|
} = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
const { white, multiplier = 0.9, class: classList } = Astro.props;
|
const { white, multiplier = 0.9, class: classList } = Astro.props
|
||||||
const sizes = [216, 396, 576, 756];
|
const sizes = [216, 396, 576, 756]
|
||||||
const borderWidths = [20, 30, 40, 50];
|
const borderWidths = [20, 30, 40, 50]
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
import Image from "astro/components/Image.astro";
|
import Image from 'astro/components/Image.astro'
|
||||||
import { Check, Github } from "lucide-astro";
|
import { Check, Github } from 'lucide-astro'
|
||||||
import { motion } from "motion/react";
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations";
|
import { getTitleAnimation } from '~/animations'
|
||||||
import ComImage from "~/assets/ComImage.png";
|
import ComImage from '~/assets/ComImage.png'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { community },
|
index: { community },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
---
|
---
|
||||||
import { motion } from "motion/react";
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations";
|
import { getTitleAnimation } from '~/animations'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
|
|
||||||
import CompactModeVideo from "~/assets/CompactMode.webm";
|
import CompactModeVideo from '~/assets/CompactMode.webm'
|
||||||
import GlanceVideo from "~/assets/Glance.webm";
|
import GlanceVideo from '~/assets/Glance.webm'
|
||||||
import SplitViewsVideo from "~/assets/SplitViews.webm";
|
import SplitViewsVideo from '~/assets/SplitViews.webm'
|
||||||
import WorkspacesVideo from "~/assets/Workspaces.webm";
|
import WorkspacesVideo from '~/assets/Workspaces.webm'
|
||||||
|
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
import Video from "./Video.astro";
|
import Video from './Video.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { features },
|
index: { features },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title1 = features.title1,
|
title1 = features.title1,
|
||||||
title2 = features.title2,
|
title2 = features.title2,
|
||||||
title3 = features.title3,
|
title3 = features.title3,
|
||||||
} = Astro.props;
|
} = Astro.props
|
||||||
|
|
||||||
const descriptions = Object.values(features.featureTabs).map(
|
const descriptions = Object.values(features.featureTabs).map(
|
||||||
(tab) => tab.description,
|
(tab) => tab.description,
|
||||||
);
|
)
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
@ -180,12 +180,12 @@ const descriptions = Object.values(features.featureTabs).map(
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const features = document.querySelectorAll(
|
const features = document.querySelectorAll(
|
||||||
'.feature, .feature-tab'
|
'.feature, .feature-tab',
|
||||||
) as NodeListOf<HTMLElement>
|
) as NodeListOf<HTMLElement>
|
||||||
|
|
||||||
// Set initial description
|
// Set initial description
|
||||||
const descriptionEl = document.querySelector(
|
const descriptionEl = document.querySelector(
|
||||||
'.feature-description'
|
'.feature-description',
|
||||||
) as HTMLDivElement
|
) as HTMLDivElement
|
||||||
const descriptions = descriptionEl?.dataset.descriptions?.split(',')
|
const descriptions = descriptionEl?.dataset.descriptions?.split(',')
|
||||||
if (descriptionEl && descriptions) {
|
if (descriptionEl && descriptions) {
|
||||||
|
@ -219,7 +219,7 @@ const descriptions = Object.values(features.featureTabs).map(
|
||||||
}
|
}
|
||||||
|
|
||||||
const videos = document.querySelectorAll(
|
const videos = document.querySelectorAll(
|
||||||
'.feature-video'
|
'.feature-video',
|
||||||
) as NodeListOf<HTMLVideoElement>
|
) as NodeListOf<HTMLVideoElement>
|
||||||
videos.forEach((vid, i) => {
|
videos.forEach((vid, i) => {
|
||||||
const yOffset = (i - index) * 20
|
const yOffset = (i - index) * 20
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from "lucide-astro";
|
import { ArrowRight } from 'lucide-astro'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Circles from "~/components/Circles.astro";
|
import Circles from '~/components/Circles.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
components: { footer },
|
components: { footer },
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
|
@ -24,7 +24,9 @@ const {
|
||||||
class="w-full text-center lg:w-1/2 lg:text-left"
|
class="w-full text-center lg:w-1/2 lg:text-left"
|
||||||
aria-labelledby="footer-title"
|
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">
|
<Description class="mx-auto max-w-xl lg:mx-0">
|
||||||
{footer.description}
|
{footer.description}
|
||||||
</Description>
|
</Description>
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from "lucide-astro";
|
import { ArrowRight } from 'lucide-astro'
|
||||||
import { motion } from "motion/react";
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations";
|
import { getTitleAnimation } from '~/animations'
|
||||||
import HomePageVideo from "~/assets/HomePageVideo.webm";
|
import HomePageVideo from '~/assets/HomePageVideo.webm'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import Title from "~/components/Title.astro";
|
import Title from '~/components/Title.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import SocialMediaStrip from "./SocialMediaStrip.astro";
|
import SocialMediaStrip from './SocialMediaStrip.astro'
|
||||||
import Video from "./Video.astro";
|
import Video from './Video.astro'
|
||||||
|
|
||||||
let titleAnimationCounter = 0;
|
let titleAnimationCounter = 0
|
||||||
function getNewAnimationDelay() {
|
function getNewAnimationDelay() {
|
||||||
titleAnimationCounter++;
|
titleAnimationCounter++
|
||||||
return titleAnimationCounter * 0.15;
|
return titleAnimationCounter * 0.15
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeroTitleAnimation() {
|
function getHeroTitleAnimation() {
|
||||||
return getTitleAnimation(getNewAnimationDelay());
|
return getTitleAnimation(getNewAnimationDelay())
|
||||||
}
|
}
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { hero },
|
index: { hero },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<header
|
<header
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
components: {
|
components: {
|
||||||
nav: { menu },
|
nav: { menu },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Hidden checkbox for menu toggle -->
|
<!-- Hidden checkbox for menu toggle -->
|
||||||
|
|
|
@ -1,245 +1,241 @@
|
||||||
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
|
||||||
faSort,
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
faSortDown,
|
import { useModsSearch } from '~/hooks/useModsSearch'
|
||||||
faSortUp,
|
import type { ZenTheme } from '~/mods'
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
import { type Locale, getUI } from '~/utils/i18n'
|
||||||
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
|
// Add icons to the library
|
||||||
library.add(faSort, faSortUp, faSortDown);
|
library.add(faSort, faSortUp, faSortDown)
|
||||||
|
|
||||||
// Create icon objects
|
// Create icon objects
|
||||||
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" });
|
const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
|
||||||
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" });
|
const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
|
||||||
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" });
|
const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
|
||||||
|
|
||||||
interface ModsListProps {
|
interface ModsListProps {
|
||||||
allMods: ZenTheme[];
|
allMods: ZenTheme[]
|
||||||
locale: Locale;
|
locale: Locale
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModsList({ allMods, locale }: ModsListProps) {
|
export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
const {
|
const {
|
||||||
search,
|
search,
|
||||||
createdSort,
|
createdSort,
|
||||||
updatedSort,
|
updatedSort,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
totalPages,
|
totalPages,
|
||||||
totalItems,
|
totalItems,
|
||||||
setSearch,
|
setSearch,
|
||||||
toggleCreatedSort,
|
toggleCreatedSort,
|
||||||
toggleUpdatedSort,
|
toggleUpdatedSort,
|
||||||
setPage,
|
setPage,
|
||||||
setLimit,
|
setLimit,
|
||||||
mods: paginatedMods,
|
mods: paginatedMods,
|
||||||
// searchParams,
|
// searchParams,
|
||||||
} = useModsSearch(allMods);
|
} = useModsSearch(allMods)
|
||||||
|
|
||||||
const [pageInput, setPageInput] = useState(page.toString());
|
const [pageInput, setPageInput] = useState(page.toString())
|
||||||
|
|
||||||
// Keep page input in sync with actual page
|
// Keep page input in sync with actual page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageInput(page.toString());
|
setPageInput(page.toString())
|
||||||
}, [page]);
|
}, [page])
|
||||||
|
|
||||||
function getSortIcon(state: "default" | "asc" | "desc") {
|
function getSortIcon(state: 'default' | 'asc' | 'desc') {
|
||||||
if (state === "asc") return ascSortIcon;
|
if (state === 'asc') return ascSortIcon
|
||||||
if (state === "desc") return descSortIcon;
|
if (state === 'desc') return descSortIcon
|
||||||
return defaultSortIcon;
|
return defaultSortIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch(e: Event) {
|
function handleSearch(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement
|
||||||
setSearch(target.value);
|
setSearch(target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLimitChange(e: Event) {
|
function handleLimitChange(e: Event) {
|
||||||
const target = e.target as HTMLSelectElement;
|
const target = e.target as HTMLSelectElement
|
||||||
setLimit(Number.parseInt(target.value, 10));
|
setLimit(Number.parseInt(target.value, 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePageSubmit(e: Event) {
|
function handlePageSubmit(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
const newPage = Number.parseInt(pageInput, 10);
|
const newPage = Number.parseInt(pageInput, 10)
|
||||||
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
||||||
setPage(newPage);
|
setPage(newPage)
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0)
|
||||||
} else {
|
} else {
|
||||||
setPageInput(page.toString());
|
setPageInput(page.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePageInputChange(e: Event) {
|
function handlePageInputChange(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement
|
||||||
setPageInput(target.value);
|
setPageInput(target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigatePage(pageNum: number) {
|
function navigatePage(pageNum: number) {
|
||||||
setPage(pageNum);
|
setPage(pageNum)
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { mods },
|
routes: { mods },
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
function renderPagination() {
|
function renderPagination() {
|
||||||
if (totalPages <= 1) return null;
|
if (totalPages <= 1) return null
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigatePage(page - 1)}
|
onClick={() => navigatePage(page - 1)}
|
||||||
className={`px-3 py-2 ${
|
className={`px-3 py-2 ${
|
||||||
page === 1
|
page === 1
|
||||||
? "pointer-events-none text-gray-400"
|
? 'pointer-events-none text-gray-400'
|
||||||
: "text-dark hover:text-gray-600"
|
: 'text-dark hover:text-gray-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
||||||
{mods.pagination.pagination.split("{input}").map((value, index) => {
|
{mods.pagination.pagination.split('{input}').map((value, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={pageInput}
|
value={pageInput}
|
||||||
onInput={handlePageInputChange}
|
onInput={handlePageInputChange}
|
||||||
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
||||||
aria-label="Page number"
|
aria-label="Page number"
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{value
|
{value
|
||||||
.replace("{totalPages}", totalPages.toString())
|
.replace('{totalPages}', totalPages.toString())
|
||||||
.replace("{totalItems}", totalItems.toString())}
|
.replace('{totalItems}', totalItems.toString())}
|
||||||
</span>
|
</span>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigatePage(page + 1)}
|
onClick={() => navigatePage(page + 1)}
|
||||||
className={`px-3 py-2 ${
|
className={`px-3 py-2 ${
|
||||||
page === totalPages
|
page === totalPages
|
||||||
? "pointer-events-none text-gray-400"
|
? 'pointer-events-none text-gray-400'
|
||||||
: "text-dark hover:text-gray-600"
|
: 'text-dark hover:text-gray-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx-auto flex flex-col items-start gap-4 px-8 lg:w-1/2">
|
<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">
|
<div className="flex w-full flex-col items-center gap-6">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="search"
|
id="search"
|
||||||
className="w-full rounded-full border-2 border-dark bg-transparent px-6 py-2 text-lg outline-none"
|
className="w-full rounded-full border-2 border-dark bg-transparent px-6 py-2 text-lg outline-none"
|
||||||
placeholder={mods.search}
|
placeholder={mods.search}
|
||||||
value={search}
|
value={search}
|
||||||
onInput={handleSearch}
|
onInput={handleSearch}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid w-full grid-cols-2 place-items-center gap-4 sm:grid-cols-3">
|
<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">
|
<div className="flex flex-col items-start gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleCreatedSort}
|
onClick={toggleCreatedSort}
|
||||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||||
>
|
>
|
||||||
{mods.sort.lastCreated}
|
{mods.sort.lastCreated}
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: getSortIcon(createdSort).html[0],
|
__html: getSortIcon(createdSort).html[0],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleUpdatedSort}
|
onClick={toggleUpdatedSort}
|
||||||
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
className="text-md flex items-center gap-2 px-4 py-2 font-semibold"
|
||||||
>
|
>
|
||||||
{mods.sort.lastUpdated}
|
{mods.sort.lastUpdated}
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: getSortIcon(updatedSort).html[0],
|
__html: getSortIcon(updatedSort).html[0],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 px-4 py-2">
|
<div className="flex items-center gap-2 px-4 py-2">
|
||||||
<label htmlFor="limit" className="text-md font-semibold">
|
<label htmlFor="limit" className="text-md font-semibold">
|
||||||
{mods.sort.perPage}
|
{mods.sort.perPage}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="limit"
|
id="limit"
|
||||||
value={limit}
|
value={limit}
|
||||||
onInput={handleLimitChange}
|
onInput={handleLimitChange}
|
||||||
className="rounded border border-dark bg-transparent px-2 py-1 text-sm [&>option]:text-black"
|
className="rounded border border-dark bg-transparent px-2 py-1 text-sm [&>option]:text-black"
|
||||||
>
|
>
|
||||||
<option value="12">12</option>
|
<option value="12">12</option>
|
||||||
<option value="24">24</option>
|
<option value="24">24</option>
|
||||||
<option value="48">48</option>
|
<option value="48">48</option>
|
||||||
<option value="96">96</option>
|
<option value="96">96</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<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.length > 0 ? (
|
||||||
paginatedMods.map((mod) => (
|
paginatedMods.map((mod) => (
|
||||||
<a
|
<a
|
||||||
key={mod.id}
|
key={mod.id}
|
||||||
href={`/mods/${mod.id}`}
|
href={`/mods/${mod.id}`}
|
||||||
className="flex flex-col gap-4 border-transparent transition-colors duration-100 hover:opacity-90"
|
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">
|
<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
|
<img
|
||||||
src={mod.image}
|
src={mod.image}
|
||||||
alt={mod.name}
|
alt={mod.name}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className="h-full w-full object-cover transition-transform duration-100 hover:scale-105"
|
className="h-full w-full object-cover transition-transform duration-100 hover:scale-105"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold">
|
<h2 className="text-lg font-bold">
|
||||||
{mod.name}{" "}
|
{mod.name}{' '}
|
||||||
<span className="ml-1 text-sm font-normal">
|
<span className="ml-1 text-sm font-normal">
|
||||||
by @{mod.author}
|
by @{mod.author}
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm font-thin">{mod.description}</p>
|
<p className="text-sm font-thin">{mod.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-4 grid place-items-center gap-4 place-self-center px-8 text-center">
|
<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>
|
<h2 className="text-lg font-bold">{mods.noResults}</h2>
|
||||||
<p className="text-sm font-thin">{mods.noResultsDescription}</p>
|
<p className="text-sm font-thin">{mods.noResultsDescription}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renderPagination()}
|
{renderPagination()}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar";
|
import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
|
||||||
import { ArrowRight, ChevronDown, Download, Menu } from "lucide-astro";
|
import { ArrowRight, ChevronDown, Download, Menu } from 'lucide-astro'
|
||||||
import { motion } from "motion/react";
|
import { motion } from 'motion/react'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import { getTitleAnimation } from "../animations.ts";
|
import { getTitleAnimation } from '../animations.ts'
|
||||||
import Logo from "./Logo.astro";
|
import Logo from './Logo.astro'
|
||||||
import MobileMenu from "./MobileMenu.astro";
|
import MobileMenu from './MobileMenu.astro'
|
||||||
import ThemeSwitch from "./ThemeSwitch.astro";
|
import ThemeSwitch from './ThemeSwitch.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
components: {
|
components: {
|
||||||
nav: { brand, menu },
|
nav: { brand, menu },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
---
|
---
|
||||||
import { Accordion, AccordionItem } from "free-astro-components";
|
import { Accordion, AccordionItem } from 'free-astro-components'
|
||||||
import { Info } from "lucide-astro";
|
import { Info } from 'lucide-astro'
|
||||||
|
|
||||||
import { releaseNotes as releaseNotesData } from "~/release-notes";
|
import { releaseNotes as releaseNotesData } from '~/release-notes'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import {
|
import {
|
||||||
type BreakingChange,
|
type BreakingChange,
|
||||||
type ReleaseNote,
|
type ReleaseNote,
|
||||||
getReleaseNoteFirefoxVersion,
|
getReleaseNoteFirefoxVersion,
|
||||||
} from "../release-notes";
|
} from '../release-notes'
|
||||||
export type Props = ReleaseNote;
|
export type Props = ReleaseNote
|
||||||
const { isTwilight, ...props } = Astro.props;
|
const { isTwilight, ...props } = Astro.props
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
releaseNotes: {
|
releaseNotes: {
|
||||||
components: { releaseNoteItem },
|
components: { releaseNoteItem },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
let date;
|
let date
|
||||||
if (props.date) {
|
if (props.date) {
|
||||||
const [day, month, year] = props.date.split("/");
|
const [day, month, year] = props.date.split('/')
|
||||||
date = new Date(Date.parse(`${year}-${month}-${day}`));
|
date = new Date(Date.parse(`${year}-${month}-${day}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const ffVersion = getReleaseNoteFirefoxVersion(props);
|
const ffVersion = getReleaseNoteFirefoxVersion(props)
|
||||||
const currentReleaseIndex = releaseNotesData.findIndex(
|
const currentReleaseIndex = releaseNotesData.findIndex(
|
||||||
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
||||||
);
|
)
|
||||||
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1];
|
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
|
||||||
let compareLink = "";
|
let compareLink = ''
|
||||||
if (prevReleaseNote && !isTwilight) {
|
if (prevReleaseNote && !isTwilight) {
|
||||||
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`;
|
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -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"
|
target="_blank"
|
||||||
aria-label={releaseNoteItem.viewIssue.replace(
|
aria-label={releaseNoteItem.viewIssue.replace(
|
||||||
'{issue}',
|
'{issue}',
|
||||||
fix.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 {
|
import {
|
||||||
faBluesky,
|
faBluesky,
|
||||||
faGithub,
|
faGithub,
|
||||||
faMastodon,
|
faMastodon,
|
||||||
faReddit,
|
faReddit,
|
||||||
faXTwitter,
|
faXTwitter,
|
||||||
} from "@fortawesome/free-brands-svg-icons";
|
} from '@fortawesome/free-brands-svg-icons'
|
||||||
|
|
||||||
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit);
|
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
||||||
const Mastodon = icon({ prefix: "fab", iconName: "mastodon" });
|
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
||||||
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" });
|
const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
|
||||||
const Github = icon({ prefix: "fab", iconName: "github" });
|
const Github = icon({ prefix: 'fab', iconName: 'github' })
|
||||||
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" });
|
const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
|
||||||
const Reddit = icon({ prefix: "fab", iconName: "reddit" });
|
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
||||||
---
|
---
|
||||||
|
|
||||||
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
import { motion } from "motion/react";
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations";
|
import { getTitleAnimation } from '~/animations'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
import tutaLogo from "~/assets/tuta-logo.png";
|
import tutaLogo from '~/assets/tuta-logo.png'
|
||||||
|
|
||||||
import Image from "astro/components/Image.astro";
|
import Image from 'astro/components/Image.astro'
|
||||||
const { showSponsors = true } = Astro.props;
|
const { showSponsors = true } = Astro.props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { sponsors },
|
index: { sponsors },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>
|
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
interface Props {
|
interface Props {
|
||||||
label?: string;
|
label?: string
|
||||||
className?: string;
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { label, className = "" } = Astro.props;
|
const { label, className = '' } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
const { class: className } = Astro.props;
|
const { class: className } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 class:list={['title text-dark', className]}>
|
<h1 class:list={['title text-dark', className]}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
const { src, class: className, ...rest } = Astro.props;
|
const { src, class: className, ...rest } = Astro.props
|
||||||
const type = src.split(".").pop() || "webm";
|
const type = src.split('.').pop() || 'webm'
|
||||||
---
|
---
|
||||||
|
|
||||||
<video
|
<video
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const I18N = {
|
export const I18N = {
|
||||||
DEFAULT_LOCALE: "en",
|
DEFAULT_LOCALE: 'en',
|
||||||
LOCALES: [{ label: "English", value: "en" }],
|
LOCALES: [{ label: 'English', value: 'en' }],
|
||||||
} as const;
|
} as const
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { I18N } from "./i18n";
|
import { I18N } from './i18n'
|
||||||
|
|
||||||
export const CONSTANT = {
|
export const CONSTANT = {
|
||||||
I18N,
|
I18N,
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,467 +1,467 @@
|
||||||
{
|
{
|
||||||
"routes": {
|
"routes": {
|
||||||
"index": {
|
"index": {
|
||||||
"title": "Zen Browser",
|
"title": "Zen Browser",
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": ["welcome", "to", "a", "calmer", "internet"],
|
"title": ["welcome", "to", "a", "calmer", "internet"],
|
||||||
"description": [
|
"description": [
|
||||||
"Beautifully designed, privacy-focused, and packed with features.",
|
"Beautifully designed, privacy-focused, and packed with features.",
|
||||||
"We care about your experience, not your data."
|
"We care about your experience, not your data."
|
||||||
],
|
],
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"beta": "Beta is now available!",
|
"beta": "Beta is now available!",
|
||||||
"support": "Support Us ❤️"
|
"support": "Support Us ❤️"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"title1": "Productivity",
|
"title1": "Productivity",
|
||||||
"title2": "at",
|
"title2": "at",
|
||||||
"title3": "its best",
|
"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.",
|
"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": {
|
"featureTabs": {
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"title": "Workspaces",
|
"title": "Workspaces",
|
||||||
"description": "Organize your tabs into Workspaces to keep your projects separate and organized, and switch between them with ease."
|
"description": "Organize your tabs into Workspaces to keep your projects separate and organized, and switch between them with ease."
|
||||||
},
|
},
|
||||||
"compactMode": {
|
"compactMode": {
|
||||||
"title": "Compact Mode",
|
"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."
|
"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": {
|
"glance": {
|
||||||
"title": "Glance",
|
"title": "Glance",
|
||||||
"description": "Glance allows you to quickly switch between your most used tabs, without having to scroll through your history."
|
"description": "Glance allows you to quickly switch between your most used tabs, without having to scroll through your history."
|
||||||
},
|
},
|
||||||
"splitView": {
|
"splitView": {
|
||||||
"title": "Split View",
|
"title": "Split View",
|
||||||
"description": "Split View allows you to view two tabs side by side, making it easier to compare and switch between them."
|
"description": "Split View allows you to view two tabs side by side, making it easier to compare and switch between them."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sponsors": {
|
"sponsors": {
|
||||||
"title": "Our 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>!",
|
"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": {
|
"sponsors": {
|
||||||
"tuta": {
|
"tuta": {
|
||||||
"name": "Tuta",
|
"name": "Tuta",
|
||||||
"url": "https://tuta.com/"
|
"url": "https://tuta.com/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"community": {
|
"community": {
|
||||||
"title": ["Our", "Core", "Values"],
|
"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.",
|
"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": {
|
"lists": {
|
||||||
"freeAndOpenSource": {
|
"freeAndOpenSource": {
|
||||||
"title": "Free and open-source",
|
"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."
|
"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": {
|
"simpleYetPowerful": {
|
||||||
"title": "Simple yet powerful",
|
"title": "Simple yet powerful",
|
||||||
"description": "Zen is simple to use, but powerful enough to handle your daily tasks."
|
"description": "Zen is simple to use, but powerful enough to handle your daily tasks."
|
||||||
},
|
},
|
||||||
"privateAndAlwaysUpToDate": {
|
"privateAndAlwaysUpToDate": {
|
||||||
"title": "Private and always up-to-date",
|
"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."
|
"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": {
|
"images": {
|
||||||
"community": {
|
"community": {
|
||||||
"alt": "Community"
|
"alt": "Community"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mods": {
|
"mods": {
|
||||||
"title": "Zen 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!",
|
"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": {
|
||||||
"pagination": "{input} of {totalPages} ({totalItems} items)"
|
"pagination": "{input} of {totalPages} ({totalItems} items)"
|
||||||
},
|
},
|
||||||
"search": "Type to search...",
|
"search": "Type to search...",
|
||||||
"sort": {
|
"sort": {
|
||||||
"lastCreated": "Last created",
|
"lastCreated": "Last created",
|
||||||
"lastUpdated": "Last updated",
|
"lastUpdated": "Last updated",
|
||||||
"perPage": "Per page"
|
"perPage": "Per page"
|
||||||
},
|
},
|
||||||
"noResults": "No results found",
|
"noResults": "No results found",
|
||||||
"noResultsDescription": "Try searching for a different term or check back later.",
|
"noResultsDescription": "Try searching for a different term or check back later.",
|
||||||
"slug": {
|
"slug": {
|
||||||
"title": "{name} - Zen Mods",
|
"title": "{name} - Zen Mods",
|
||||||
"description": "Learn more about {name} mod available on Zen Browser",
|
"description": "Learn more about {name} mod available on Zen Browser",
|
||||||
"alert": {
|
"alert": {
|
||||||
"description": "You need to have Zen Browser installed to install this theme.",
|
"description": "You need to have Zen Browser installed to install this theme.",
|
||||||
"button": "Download now!"
|
"button": "Download now!"
|
||||||
},
|
},
|
||||||
"createdBy": "Created by <a href={link} class=\"zen-link font-bold\">{author}</a> • <span class=\"font-bold\">v{version}</span>",
|
"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>",
|
"creationDate": "Creation date • <b>{createdAt}</b>",
|
||||||
"latestUpdate": "Latest update • <b>{updatedAt}</b>",
|
"latestUpdate": "Latest update • <b>{updatedAt}</b>",
|
||||||
"visitModHomepage": "Visit mod homepage",
|
"visitModHomepage": "Visit mod homepage",
|
||||||
"installMod": "Install Mod 🎉",
|
"installMod": "Install Mod 🎉",
|
||||||
"uninstallMod": "Uninstall Mod",
|
"uninstallMod": "Uninstall Mod",
|
||||||
"back": "Back"
|
"back": "Back"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"releaseNotes": {
|
"releaseNotes": {
|
||||||
"title": "Release notes - Zen Browser",
|
"title": "Release notes - Zen Browser",
|
||||||
"topSection": {
|
"topSection": {
|
||||||
"title": "Release Notes",
|
"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! ❤️"
|
"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": {
|
"list": {
|
||||||
"support": "Give us some support!",
|
"support": "Give us some support!",
|
||||||
"expandAll": "Expand all",
|
"expandAll": "Expand all",
|
||||||
"navigateToVersion": "Navigate to version..."
|
"navigateToVersion": "Navigate to version..."
|
||||||
},
|
},
|
||||||
"backToTop": "Back to the top",
|
"backToTop": "Back to the top",
|
||||||
"chooseVersion": "Choose version",
|
"chooseVersion": "Choose version",
|
||||||
"components": {
|
"components": {
|
||||||
"releaseNoteItem": {
|
"releaseNoteItem": {
|
||||||
"twilight": "Twilight",
|
"twilight": "Twilight",
|
||||||
"twilightChanges": "Twilight changes for {version} 🌙",
|
"twilightChanges": "Twilight changes for {version} 🌙",
|
||||||
"releaseChanges": "Release notes for {version} 🎉",
|
"releaseChanges": "Release notes for {version} 🎉",
|
||||||
"firefoxVersion": "Firefox {version}",
|
"firefoxVersion": "Firefox {version}",
|
||||||
"githubRelease": "GitHub Release",
|
"githubRelease": "GitHub Release",
|
||||||
"workflowRun": "Workflow run",
|
"workflowRun": "Workflow run",
|
||||||
"compareChanges": "Compare changes",
|
"compareChanges": "Compare changes",
|
||||||
"twilightWarning": "Please note that Twilight is a pre-release version of Zen Browser. It may contain bugs and unfinished features.",
|
"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>.",
|
"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": {
|
"sections": {
|
||||||
"fixes": "Fixes",
|
"fixes": "Fixes",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"themeChanges": "Theme Changes",
|
"themeChanges": "Theme Changes",
|
||||||
"breakingChanges": {
|
"breakingChanges": {
|
||||||
"title": "Breaking Changes",
|
"title": "Breaking Changes",
|
||||||
"description": "View breaking changes on GitHub"
|
"description": "View breaking changes on GitHub"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"learnMore": "Learn more",
|
"learnMore": "Learn more",
|
||||||
"viewIssue": "View issue number {issue} on GitHub"
|
"viewIssue": "View issue number {issue} on GitHub"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"title": "Release notes",
|
"title": "Release notes",
|
||||||
"redirect": "Redirecting to release notes for version {version}..."
|
"redirect": "Redirecting to release notes for version {version}..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "About - Zen Browser",
|
"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.",
|
"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?",
|
"littleHelp": "A little help?",
|
||||||
"mainTeam": {
|
"mainTeam": {
|
||||||
"title": "Main Team",
|
"title": "Main Team",
|
||||||
"description": "This list shows the main team members who are working hard to bring you the best browsing experience.",
|
"description": "This list shows the main team members who are working hard to bring you the best browsing experience.",
|
||||||
"members": {
|
"members": {
|
||||||
"mauro": {
|
"mauro": {
|
||||||
"name": "Mauro B.",
|
"name": "Mauro B.",
|
||||||
"description": "Creator, Main Developer",
|
"description": "Creator, Main Developer",
|
||||||
"link": "https://cheff.dev/"
|
"link": "https://cheff.dev/"
|
||||||
},
|
},
|
||||||
"oscar": {
|
"oscar": {
|
||||||
"name": "Oscar Gonzalez",
|
"name": "Oscar Gonzalez",
|
||||||
"description": "Site Reliability Engineer (SRE) and code signing.",
|
"description": "Site Reliability Engineer (SRE) and code signing.",
|
||||||
"link": false
|
"link": false
|
||||||
},
|
},
|
||||||
"jan": {
|
"jan": {
|
||||||
"name": "Jan Heres",
|
"name": "Jan Heres",
|
||||||
"description": "Active contributor and helps with MacOS builds",
|
"description": "Active contributor and helps with MacOS builds",
|
||||||
"link": "https://janheres.eu/"
|
"link": "https://janheres.eu/"
|
||||||
},
|
},
|
||||||
"brhm": {
|
"brhm": {
|
||||||
"name": "BrhmDev",
|
"name": "BrhmDev",
|
||||||
"description": "Active contributor with great contributions",
|
"description": "Active contributor with great contributions",
|
||||||
"link": "https://github.com/BrhmDev"
|
"link": "https://github.com/BrhmDev"
|
||||||
},
|
},
|
||||||
"canoa": {
|
"canoa": {
|
||||||
"name": "Canoa",
|
"name": "Canoa",
|
||||||
"description": "Active contributor, and very active in issue handling and website management",
|
"description": "Active contributor, and very active in issue handling and website management",
|
||||||
"link": "https://thatcanoa.org/"
|
"link": "https://thatcanoa.org/"
|
||||||
},
|
},
|
||||||
"adam": {
|
"adam": {
|
||||||
"name": "Adam",
|
"name": "Adam",
|
||||||
"description": "Branding and design",
|
"description": "Branding and design",
|
||||||
"link": "https://cybrneon.xyz/"
|
"link": "https://cybrneon.xyz/"
|
||||||
},
|
},
|
||||||
"kristijanribaric": {
|
"kristijanribaric": {
|
||||||
"name": "Kristijan Ribaric",
|
"name": "Kristijan Ribaric",
|
||||||
"description": "Active contributor",
|
"description": "Active contributor",
|
||||||
"link": "https://github.com/kristijanribaric"
|
"link": "https://github.com/kristijanribaric"
|
||||||
},
|
},
|
||||||
"n7itro": {
|
"n7itro": {
|
||||||
"name": "n7itro",
|
"name": "n7itro",
|
||||||
"description": "Active contributor and release notes writer",
|
"description": "Active contributor and release notes writer",
|
||||||
"link": "https://github.com/n7itro"
|
"link": "https://github.com/n7itro"
|
||||||
},
|
},
|
||||||
"bryan": {
|
"bryan": {
|
||||||
"name": "Bryan Galdámez",
|
"name": "Bryan Galdámez",
|
||||||
"description": "Huge contributor on theme functionalities",
|
"description": "Huge contributor on theme functionalities",
|
||||||
"link": "https://josuegalre.netlify.app/"
|
"link": "https://josuegalre.netlify.app/"
|
||||||
},
|
},
|
||||||
"jafeth": {
|
"jafeth": {
|
||||||
"name": "Jafeth Garro",
|
"name": "Jafeth Garro",
|
||||||
"description": "Documentation writer",
|
"description": "Documentation writer",
|
||||||
"link": "https://iamjafeth.com/"
|
"link": "https://iamjafeth.com/"
|
||||||
},
|
},
|
||||||
"larvey": {
|
"larvey": {
|
||||||
"name": "Larvey",
|
"name": "Larvey",
|
||||||
"description": "AUR maintainer",
|
"description": "AUR maintainer",
|
||||||
"link": "https://github.com/LarveyOfficial/"
|
"link": "https://github.com/LarveyOfficial/"
|
||||||
},
|
},
|
||||||
"daniel": {
|
"daniel": {
|
||||||
"name": "Daniel García",
|
"name": "Daniel García",
|
||||||
"description": "MacOS certificate and app notarization maintainer",
|
"description": "MacOS certificate and app notarization maintainer",
|
||||||
"link": false
|
"link": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"contributors": {
|
"contributors": {
|
||||||
"title": "Contributors",
|
"title": "Contributors",
|
||||||
"description": "This list shows the contributors who have helped us to make Zen Browser the best it can be."
|
"description": "This list shows the contributors who have helped us to make Zen Browser the best it can be."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"donate": {
|
"donate": {
|
||||||
"title": "Donate - Zen Browser",
|
"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.",
|
"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": {
|
"patreon": {
|
||||||
"title": "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."
|
"description": "Patreon allows you to support us with a monthly donation. You can choose the level of support that works best for you."
|
||||||
},
|
},
|
||||||
"koFi": {
|
"koFi": {
|
||||||
"title": "Ko-fi",
|
"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.",
|
"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"
|
"button": "Go to Ko-fi"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"title": "Download - Zen Browser",
|
"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.",
|
"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.",
|
"twilightInfo": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates.",
|
||||||
"alertInfo": {
|
"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."
|
"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": {
|
"platformSelector": {
|
||||||
"title": "Platform Selector",
|
"title": "Platform Selector",
|
||||||
"description": "Select your platform to download Zen Browser."
|
"description": "Select your platform to download Zen Browser."
|
||||||
},
|
},
|
||||||
"additionalResources": {
|
"additionalResources": {
|
||||||
"title": "Additional Resources",
|
"title": "Additional Resources",
|
||||||
"sourceCode": {
|
"sourceCode": {
|
||||||
"title": "Source Code",
|
"title": "Source Code",
|
||||||
"description": "Explore Zen Browser's source code on GitHub. Contribute to the project or build your own version."
|
"description": "Explore Zen Browser's source code on GitHub. Contribute to the project or build your own version."
|
||||||
},
|
},
|
||||||
"documentation": {
|
"documentation": {
|
||||||
"title": "Documentation",
|
"title": "Documentation",
|
||||||
"description": "Access comprehensive documentation, guides, and tutorials for Zen Browser."
|
"description": "Access comprehensive documentation, guides, and tutorials for Zen Browser."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityNotice": {
|
"securityNotice": {
|
||||||
"title": "Verified & Secure Downloads",
|
"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>."
|
"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": {
|
"platformNames": {
|
||||||
"mac": "macOS",
|
"mac": "macOS",
|
||||||
"windows": "Windows",
|
"windows": "Windows",
|
||||||
"linux": "Linux",
|
"linux": "Linux",
|
||||||
"macDownload": "MacOS Download",
|
"macDownload": "MacOS Download",
|
||||||
"windowsDownload": "Windows Download",
|
"windowsDownload": "Windows Download",
|
||||||
"linuxDownload": "Linux Download"
|
"linuxDownload": "Linux Download"
|
||||||
},
|
},
|
||||||
"platformDescriptions": {
|
"platformDescriptions": {
|
||||||
"mac": "Works on both new Apple (M-Series) and older Intel Macs.<br />Requires macOS 11.0 or later.",
|
"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.",
|
"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."
|
"linux": "Works with many Linux versions.<br />Pick the download that matches your system."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"privacyPolicy": {
|
"privacyPolicy": {
|
||||||
"title": "Privacy Policy",
|
"title": "Privacy Policy",
|
||||||
"lastUpdated": "Last updated: 2025-02-5",
|
"lastUpdated": "Last updated: 2025-02-5",
|
||||||
"sections": {
|
"sections": {
|
||||||
"introduction": {
|
"introduction": {
|
||||||
"title": "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.",
|
"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"
|
"summary": "We don't sell data - We don't collect data - We don't track you"
|
||||||
},
|
},
|
||||||
"noCollect": {
|
"noCollect": {
|
||||||
"title": "1. Information We Do Not Collect",
|
"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:"
|
"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": {
|
"noTelemetry": {
|
||||||
"title": "1.1. No Telemetry",
|
"title": "1.1. No Telemetry",
|
||||||
"body": "We do not collect any telemetry data or crash reports.",
|
"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."
|
"body2": "Zen Browser has stripped out telemetry built into Mozilla Firefox. We have removed all telemetry data collection and crash reports."
|
||||||
},
|
},
|
||||||
"noPersonalData": {
|
"noPersonalData": {
|
||||||
"title": "1.2. No Personal Data Collection",
|
"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."
|
"body": "Zen Browser does not collect any personal information such as your IP address, browsing history, search queries, or form data."
|
||||||
},
|
},
|
||||||
"noThirdParty": {
|
"noThirdParty": {
|
||||||
"title": "1.3. No Third-Party Tracking",
|
"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."
|
"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": {
|
"externalConnections": {
|
||||||
"title": "1.4. External connections made at startup",
|
"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)."
|
"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": {
|
"localStorage": {
|
||||||
"title": "2. Information Stored Locally on Your Device"
|
"title": "2. Information Stored Locally on Your Device"
|
||||||
},
|
},
|
||||||
"browsingData": {
|
"browsingData": {
|
||||||
"title": "2.1. Browsing Data",
|
"title": "2.1. Browsing Data",
|
||||||
"body": "Zen Browser stores certain data locally on your device to enhance your browsing experience. This includes:"
|
"body": "Zen Browser stores certain data locally on your device to enhance your browsing experience. This includes:"
|
||||||
},
|
},
|
||||||
"cookies": {
|
"cookies": {
|
||||||
"title": "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."
|
"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": {
|
"cache": {
|
||||||
"title": "Cache and Temporary Files",
|
"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."
|
"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": {
|
"settings": {
|
||||||
"title": "2.2. Settings and Preferences",
|
"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."
|
"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": {
|
"sync": {
|
||||||
"title": "3. Sync Feature",
|
"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.",
|
"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",
|
"link1": "Mozilla Firefox Sync",
|
||||||
"link2": "This is how we store your passwords"
|
"link2": "This is how we store your passwords"
|
||||||
},
|
},
|
||||||
"addons": {
|
"addons": {
|
||||||
"title": "4. Add-ons and \"Mods\"",
|
"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."
|
"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": {
|
"security": {
|
||||||
"title": "5. Data 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.",
|
"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."
|
"note": "Note that most of the security measures are taken care by Mozilla Firefox."
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"title": "6. Your Control",
|
"title": "6. Your Control",
|
||||||
"deletionTitle": "6.1. Data Deletion",
|
"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."
|
"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": {
|
"website": {
|
||||||
"title": "7. Our Website and Services",
|
"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.",
|
"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",
|
"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."
|
"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": {
|
"changes": {
|
||||||
"title": "8. Changes to This Privacy Policy",
|
"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."
|
"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": {
|
"otherTelemetry": {
|
||||||
"title": "9. Other telemetry done by Mozilla Firefox",
|
"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.",
|
"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",
|
"firefoxPrivacyNotice": "Firefox Privacy Notice",
|
||||||
"forMoreInformation": "for more information."
|
"forMoreInformation": "for more information."
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "10. Contact Us",
|
"title": "10. Contact Us",
|
||||||
"body": "If you have any questions or concerns about this Privacy Policy or Zen Browser, please contact us at:",
|
"body": "If you have any questions or concerns about this Privacy Policy or Zen Browser, please contact us at:",
|
||||||
"discord": "Discord: ",
|
"discord": "Discord: ",
|
||||||
"discordLink": "Zen Browser's Discord",
|
"discordLink": "Zen Browser's Discord",
|
||||||
"github": "GitHub: ",
|
"github": "GitHub: ",
|
||||||
"githubLink": "Organization"
|
"githubLink": "Organization"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"title": ["Welcome", "to", "Zen!"]
|
"title": ["Welcome", "to", "Zen!"]
|
||||||
},
|
},
|
||||||
"whatsNew": {
|
"whatsNew": {
|
||||||
"title": "What's New in {latestVersion.version}!",
|
"title": "What's New in {latestVersion.version}!",
|
||||||
"reportIssue": "Report an issue",
|
"reportIssue": "Report an issue",
|
||||||
"joinDiscord": "Join our Discord",
|
"joinDiscord": "Join our Discord",
|
||||||
"readFullReleaseNotes": "Read the full release notes"
|
"readFullReleaseNotes": "Read the full release notes"
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Page Not Found",
|
"title": "Page Not Found",
|
||||||
"description": "Sorry, the page you are looking for does not exist or has been moved.",
|
"description": "Sorry, the page you are looking for does not exist or has been moved.",
|
||||||
"button": "Go Home"
|
"button": "Go Home"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"index": {
|
"index": {
|
||||||
"title": "Zen Browser",
|
"title": "Zen Browser",
|
||||||
"description": "Beautifully designed, privacy-focused, and packed with features."
|
"description": "Beautifully designed, privacy-focused, and packed with features."
|
||||||
},
|
},
|
||||||
"mods": {
|
"mods": {
|
||||||
"title": "Zen 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!"
|
"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": {
|
"releaseNotes": {
|
||||||
"title": "Release notes - Zen",
|
"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! ❤️"
|
"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": {
|
"about": {
|
||||||
"title": "About - Zen",
|
"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."
|
"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": {
|
"donate": {
|
||||||
"title": "Donate - Zen",
|
"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."
|
"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": {
|
"download": {
|
||||||
"title": "Download - Zen",
|
"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."
|
"description": "Download Zen Browser for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification."
|
||||||
},
|
},
|
||||||
"privacyPolicy": {
|
"privacyPolicy": {
|
||||||
"title": "Privacy Policy - Zen",
|
"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."
|
"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": {
|
"welcome": {
|
||||||
"title": "Welcome!",
|
"title": "Welcome!",
|
||||||
"description": "Welcome to Zen!"
|
"description": "Welcome to Zen!"
|
||||||
},
|
},
|
||||||
"whatsNew": {
|
"whatsNew": {
|
||||||
"title": "What's New in {latestVersion.version}!"
|
"title": "What's New in {latestVersion.version}!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"footer": {
|
"footer": {
|
||||||
"title": "Zen Browser",
|
"title": "Zen Browser",
|
||||||
"description": "Beautifully designed, privacy-focused, and packed with features. We care about your experience, not your data.",
|
"description": "Beautifully designed, privacy-focused, and packed with features. We care about your experience, not your data.",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"followUs": "Follow Us",
|
"followUs": "Follow Us",
|
||||||
"aboutUs": "About Us",
|
"aboutUs": "About Us",
|
||||||
"teamAndContributors": "Team & Contributors",
|
"teamAndContributors": "Team & Contributors",
|
||||||
"privacyPolicy": "Privacy Policy",
|
"privacyPolicy": "Privacy Policy",
|
||||||
"getStarted": "Get Started",
|
"getStarted": "Get Started",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"zenMods": "Zen Mods",
|
"zenMods": "Zen Mods",
|
||||||
"releaseNotes": "Release Notes",
|
"releaseNotes": "Release Notes",
|
||||||
"getHelp": "Get Help",
|
"getHelp": "Get Help",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"uptimeStatus": "Uptime Status",
|
"uptimeStatus": "Uptime Status",
|
||||||
"reportAnIssue": "Report an Issue",
|
"reportAnIssue": "Report an Issue",
|
||||||
"twilight": "Twilight",
|
"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>"
|
"madeWith": "Made with <span aria-label='love'>❤️</span> by the <a href='{link}' class='zen-link inline-block font-bold'>Zen Team</a>"
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"brand": "Zen Browser",
|
"brand": "Zen Browser",
|
||||||
"menu": {
|
"menu": {
|
||||||
"gettingStarted": "Getting Started",
|
"gettingStarted": "Getting Started",
|
||||||
"usefulLinks": "Useful Links",
|
"usefulLinks": "Useful Links",
|
||||||
"mods": "Mods",
|
"mods": "Mods",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"releaseNotes": "Release Notes",
|
"releaseNotes": "Release Notes",
|
||||||
"zenMods": "Zen Mods",
|
"zenMods": "Zen Mods",
|
||||||
"tryZenMods": "Try Zen Mods",
|
"tryZenMods": "Try Zen Mods",
|
||||||
"zenModsDesc": "Customize your browsing experience with Zen Mods.",
|
"zenModsDesc": "Customize your browsing experience with Zen Mods.",
|
||||||
"releaseNotesDesc": "Stay up to date with the latest features and improvements.",
|
"releaseNotesDesc": "Stay up to date with the latest features and improvements.",
|
||||||
"discordDesc": "Join our community on Discord to chat with other Zen users!",
|
"discordDesc": "Join our community on Discord to chat with other Zen users!",
|
||||||
"donate": "Donate ❤️",
|
"donate": "Donate ❤️",
|
||||||
"donateDesc": "Support the development of Zen Browser with a donation.",
|
"donateDesc": "Support the development of Zen Browser with a donation.",
|
||||||
"aboutUs": "About Us 🌟",
|
"aboutUs": "About Us 🌟",
|
||||||
"aboutUsDesc": "Learn more about the team behind Zen Browser.",
|
"aboutUsDesc": "Learn more about the team behind Zen Browser.",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"documentationDesc": "Learn how to use Zen Browser with our documentation.",
|
"documentationDesc": "Learn how to use Zen Browser with our documentation.",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"githubDesc": "Contribute to the development of Zen Browser on GitHub.",
|
"githubDesc": "Contribute to the development of Zen Browser on GitHub.",
|
||||||
"menu": "Menu"
|
"menu": "Menu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import NotFound from "./[...locale]/404.astro";
|
import NotFound from './[...locale]/404.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import Title from "~/components/Title.astro";
|
import Title from '~/components/Title.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale);
|
const getLocalePath = getPath(locale)
|
||||||
const {
|
const {
|
||||||
routes: { notFound },
|
routes: { notFound },
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={notFound.title}>
|
<Layout title={notFound.title}>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from "lucide-astro";
|
import { ArrowRight } from 'lucide-astro'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const {
|
const {
|
||||||
routes: { donate },
|
routes: { donate },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.donate.title} description={layout.donate.description}>
|
<Layout title={layout.donate.title} description={layout.donate.description}>
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
---
|
---
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import DownloadScript from "~/components/download/DownloadScript.astro";
|
import DownloadScript from '~/components/download/DownloadScript.astro'
|
||||||
import PlatformDownload from "~/components/download/PlatformDownload.astro";
|
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
||||||
import { getReleasesWithChecksums } from "~/components/download/release-data.astro";
|
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getChecksums } from "~/utils/githubChecksums";
|
import { getChecksums } from '~/utils/githubChecksums'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faApple,
|
faApple,
|
||||||
faGithub,
|
faGithub,
|
||||||
faLinux,
|
faLinux,
|
||||||
faWindows,
|
faWindows,
|
||||||
} from "@fortawesome/free-brands-svg-icons";
|
} from '@fortawesome/free-brands-svg-icons'
|
||||||
import { ExternalLink, Lock } from "lucide-astro";
|
import { ExternalLink, Lock } from 'lucide-astro'
|
||||||
|
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
const {
|
const {
|
||||||
routes: { download },
|
routes: { download },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
library.add(faWindows, faLinux, faApple, faGithub);
|
library.add(faWindows, faLinux, faApple, faGithub)
|
||||||
const windowsIcon = icon({ prefix: "fab", iconName: "windows" });
|
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
||||||
const linuxIcon = icon({ prefix: "fab", iconName: "linux" });
|
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
||||||
const appleIcon = icon({ prefix: "fab", iconName: "apple" });
|
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
||||||
const githubIcon = icon({ prefix: "fab", iconName: "github" });
|
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
|
||||||
|
|
||||||
const checksums = await getChecksums();
|
const checksums = await getChecksums()
|
||||||
const releases = getReleasesWithChecksums(checksums);
|
const releases = getReleasesWithChecksums(checksums)
|
||||||
|
|
||||||
const platformNames = download.platformNames;
|
const platformNames = download.platformNames
|
||||||
const platformDescriptions = download.platformDescriptions;
|
const platformDescriptions = download.platformDescriptions
|
||||||
---
|
---
|
||||||
|
|
||||||
<DownloadScript />
|
<DownloadScript />
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import rss, { type RSSOptions } from "@astrojs/rss";
|
import rss, { type RSSOptions } from '@astrojs/rss'
|
||||||
import { releaseNotes } from "~/release-notes";
|
import { releaseNotes } from '~/release-notes'
|
||||||
import type { ReleaseNote } from "~/release-notes";
|
import type { ReleaseNote } from '~/release-notes'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
/** The default number of entries to include in the RSS feed. */
|
/** The default number of entries to include in the RSS feed. */
|
||||||
const RSS_ENTRY_LIMIT = 20;
|
const RSS_ENTRY_LIMIT = 20
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the GET request for the `feed.xml` endpoint.
|
* Handles the GET request for the `feed.xml` endpoint.
|
||||||
* @returns The RSS feed for the Zen Browser release notes.
|
* @returns The RSS feed for the Zen Browser release notes.
|
||||||
*/
|
*/
|
||||||
export function GET(context: any) {
|
export function GET(context: any) {
|
||||||
// Just in case the release notes array is empty for whatever reason.
|
// Just in case the release notes array is empty for whatever reason.
|
||||||
const latestDate =
|
const latestDate =
|
||||||
releaseNotes.length > 0
|
releaseNotes.length > 0
|
||||||
? formatRssDate(releaseNotes[0].date as string)
|
? formatRssDate(releaseNotes[0].date as string)
|
||||||
: new Date();
|
: new Date()
|
||||||
|
|
||||||
const rssData: RSSOptions = {
|
const rssData: RSSOptions = {
|
||||||
title: "Zen Browser Release Notes",
|
title: 'Zen Browser Release Notes',
|
||||||
description: "Release Notes for the Zen Browser",
|
description: 'Release Notes for the Zen Browser',
|
||||||
site: context.url,
|
site: context.url,
|
||||||
items: [],
|
items: [],
|
||||||
customData: `
|
customData: `
|
||||||
<language>en</language>
|
<language>en</language>
|
||||||
<link>https://www.zen-browser.app/release-notes</link>
|
<link>https://www.zen-browser.app/release-notes</link>
|
||||||
<copyright>Zen Browser © ${new Date().getFullYear()} - Made with ❤️ by the Zen team.</copyright>
|
<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>
|
<link>https://www.zen-browser.app</link>
|
||||||
</image>
|
</image>
|
||||||
`,
|
`,
|
||||||
};
|
}
|
||||||
|
|
||||||
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
||||||
rssData.items.push({
|
rssData.items.push({
|
||||||
title: `Release notes for version ${releaseNote.version}`,
|
title: `Release notes for version ${releaseNote.version}`,
|
||||||
link: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
link: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
||||||
pubDate: formatRssDate(releaseNote.date as string),
|
pubDate: formatRssDate(releaseNote.date as string),
|
||||||
description: releaseNote.extra,
|
description: releaseNote.extra,
|
||||||
content: formatReleaseNote(releaseNote),
|
content: formatReleaseNote(releaseNote),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return rss(rssData);
|
return rss(rssData)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,15 +56,15 @@ export function GET(context: any) {
|
||||||
* @returns The passed in date string as a Date object.
|
* @returns The passed in date string as a Date object.
|
||||||
*/
|
*/
|
||||||
function formatRssDate(dateStr: string) {
|
function formatRssDate(dateStr: string) {
|
||||||
const splitDate = dateStr.split("/");
|
const splitDate = dateStr.split('/')
|
||||||
if (splitDate.length !== 3) {
|
if (splitDate.length !== 3) {
|
||||||
throw new Error("Invalid date format");
|
throw new Error('Invalid date format')
|
||||||
}
|
}
|
||||||
|
|
||||||
const day = Number(splitDate[0]);
|
const day = Number(splitDate[0])
|
||||||
const month = Number(splitDate[1]) - 1;
|
const month = Number(splitDate[1]) - 1
|
||||||
const year = Number(splitDate[2]);
|
const year = Number(splitDate[2])
|
||||||
return new Date(year, month, day);
|
return new Date(year, month, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,102 +73,102 @@ function formatRssDate(dateStr: string) {
|
||||||
* @returns The formatted release note as a HTML string.
|
* @returns The formatted release note as a HTML string.
|
||||||
*/
|
*/
|
||||||
function formatReleaseNote(releaseNote: ReleaseNote) {
|
function formatReleaseNote(releaseNote: ReleaseNote) {
|
||||||
let content = `<p>
|
let content = `<p>
|
||||||
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>.
|
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>.
|
||||||
Thanks everyone for your feedback! ❤️
|
Thanks everyone for your feedback! ❤️
|
||||||
</p>`;
|
</p>`
|
||||||
|
|
||||||
if (releaseNote.image) {
|
if (releaseNote.image) {
|
||||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||||
alt="Release Image for version ${releaseNote.version}"
|
alt="Release Image for version ${releaseNote.version}"
|
||||||
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
||||||
/>`;
|
/>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (releaseNote.extra) {
|
if (releaseNote.extra) {
|
||||||
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`;
|
content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
|
||||||
}
|
}
|
||||||
|
|
||||||
content += addReleaseNoteSection(
|
content += addReleaseNoteSection(
|
||||||
"⚠️ Breaking changes",
|
'⚠️ Breaking changes',
|
||||||
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
|
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
|
||||||
);
|
)
|
||||||
content += addReleaseNoteSection(
|
content += addReleaseNoteSection(
|
||||||
"✓ Fixes",
|
'✓ Fixes',
|
||||||
releaseNote.fixes?.map(fixToReleaseNote),
|
releaseNote.fixes?.map(fixToReleaseNote),
|
||||||
);
|
)
|
||||||
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges);
|
content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
|
||||||
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
|
content += addReleaseNoteSection('⭐ Features', releaseNote.features)
|
||||||
|
|
||||||
return content;
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
function addReleaseNoteSection(title: string, items?: string[]): string {
|
function addReleaseNoteSection(title: string, items?: string[]): string {
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = `<h2>${title}</h2>`;
|
let content = `<h2>${title}</h2>`
|
||||||
content += `<ul>`;
|
content += `<ul>`
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item && item.length > 0) {
|
if (item && item.length > 0) {
|
||||||
content += `<li>${item}</li>`;
|
content += `<li>${item}</li>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content += `</ul>`;
|
content += `</ul>`
|
||||||
return content;
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixToReleaseNote(
|
function fixToReleaseNote(
|
||||||
fix?: Exclude<ReleaseNote["fixes"], undefined>[number],
|
fix?: Exclude<ReleaseNote['fixes'], undefined>[number],
|
||||||
) {
|
) {
|
||||||
if (typeof fix === "string") {
|
if (typeof fix === 'string') {
|
||||||
return fix;
|
return fix
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fix || !fix.description || fix.description.length === 0) {
|
if (!fix || !fix.description || fix.description.length === 0) {
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = fix.description;
|
let note = fix.description
|
||||||
if (fix.issue) {
|
if (fix.issue) {
|
||||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
|
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`
|
||||||
}
|
}
|
||||||
return note;
|
return note
|
||||||
}
|
}
|
||||||
|
|
||||||
function breakingChangeToReleaseNote(
|
function breakingChangeToReleaseNote(
|
||||||
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number],
|
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number],
|
||||||
) {
|
) {
|
||||||
if (typeof breakingChange === "string") {
|
if (typeof breakingChange === 'string') {
|
||||||
return breakingChange;
|
return breakingChange
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!breakingChange ||
|
!breakingChange ||
|
||||||
!breakingChange.description ||
|
!breakingChange.description ||
|
||||||
breakingChange.description.length === 0
|
breakingChange.description.length === 0
|
||||||
) {
|
) {
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`;
|
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
|
||||||
}
|
}
|
||||||
|
|
||||||
function pubDate(date?: Date) {
|
function pubDate(date?: Date) {
|
||||||
date ??= new Date();
|
date ??= new Date()
|
||||||
|
|
||||||
const pieces = date.toString().split(" ");
|
const pieces = date.toString().split(' ')
|
||||||
const offsetTime = pieces[5].match(/[-+]\d{4}/);
|
const offsetTime = pieces[5].match(/[-+]\d{4}/)
|
||||||
const offset = offsetTime ? offsetTime : pieces[5];
|
const offset = offsetTime ? offsetTime : pieces[5]
|
||||||
const parts = [
|
const parts = [
|
||||||
pieces[0] + ",",
|
pieces[0] + ',',
|
||||||
pieces[2],
|
pieces[2],
|
||||||
pieces[1],
|
pieces[1],
|
||||||
pieces[3],
|
pieces[3],
|
||||||
pieces[4],
|
pieces[4],
|
||||||
offset,
|
offset,
|
||||||
];
|
]
|
||||||
|
|
||||||
return parts.join(" ");
|
return parts.join(' ')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Community from "~/components/Community.astro";
|
import Community from '~/components/Community.astro'
|
||||||
import Features from "~/components/Features.astro";
|
import Features from '~/components/Features.astro'
|
||||||
import Hero from "~/components/Hero.astro";
|
import Hero from '~/components/Hero.astro'
|
||||||
import Sponsors from "~/components/Sponsors.astro";
|
import Sponsors from '~/components/Sponsors.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const { layout } = getUI(locale);
|
const { layout } = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight, Info } from "lucide-astro";
|
import { ArrowRight, Info } from 'lucide-astro'
|
||||||
import BackButton from "~/components/BackButton.astro";
|
import BackButton from '~/components/BackButton.astro'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods";
|
import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
|
||||||
import { getUI } from "~/utils/i18n";
|
import { getUI } from '~/utils/i18n'
|
||||||
import { getLocale, getOtherLocales } from "~/utils/i18n";
|
import { getLocale, getOtherLocales } from '~/utils/i18n'
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const mods = await getAllMods();
|
const mods = await getAllMods()
|
||||||
return mods.flatMap((mod) => [
|
return mods.flatMap((mod) => [
|
||||||
...getOtherLocales().map((locale) => ({
|
...getOtherLocales().map((locale) => ({
|
||||||
params: {
|
params: {
|
||||||
slug: mod.id,
|
slug: mod.id,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
...mod,
|
...mod,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
slug: mod.id,
|
slug: mod.id,
|
||||||
locale: undefined,
|
locale: undefined,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
...mod,
|
...mod,
|
||||||
locale: undefined,
|
locale: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
|
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
|
||||||
|
|
||||||
const mod = Astro.props;
|
const mod = Astro.props
|
||||||
|
|
||||||
const dates = {
|
const dates = {
|
||||||
createdAt: getLocalizedDate(mod.createdAt),
|
createdAt: getLocalizedDate(mod.createdAt),
|
||||||
updatedAt: getLocalizedDate(mod.updatedAt),
|
updatedAt: getLocalizedDate(mod.updatedAt),
|
||||||
};
|
}
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
mods: { slug },
|
mods: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
@ -104,7 +104,7 @@ const {
|
||||||
<p
|
<p
|
||||||
set:html={slug.latestUpdate.replace(
|
set:html={slug.latestUpdate.replace(
|
||||||
'{updatedAt}',
|
'{updatedAt}',
|
||||||
dates.updatedAt
|
dates.updatedAt,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import ModsList from "~/components/ModsList";
|
import ModsList from '~/components/ModsList'
|
||||||
import { CONSTANT } from "~/constants";
|
import { CONSTANT } from '~/constants'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getAllMods } from "~/mods";
|
import { getAllMods } from '~/mods'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { mods },
|
routes: { mods },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
const allMods = (await getAllMods()) || [];
|
const allMods = (await getAllMods()) || []
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.mods.title}>
|
<Layout title={layout.mods.title}>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Title from "~/components/Title.astro";
|
import Title from '~/components/Title.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { privacyPolicy },
|
routes: { privacyPolicy },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
---
|
---
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { releaseNotes } from "~/release-notes";
|
import { releaseNotes } from '~/release-notes'
|
||||||
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n";
|
import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
releaseNotes: { slug },
|
releaseNotes: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const i18nPaths = getI18nPaths();
|
const i18nPaths = getI18nPaths()
|
||||||
|
|
||||||
return i18nPaths.flatMap(({ params: { locale } }) => [
|
return i18nPaths.flatMap(({ params: { locale } }) => [
|
||||||
...releaseNotes.map((release: any) => ({
|
...releaseNotes.map((release: any) => ({
|
||||||
params: { slug: release.version, locale },
|
params: { slug: release.version, locale },
|
||||||
props: { ...release },
|
props: { ...release },
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
params: { slug: "latest", locale },
|
params: { slug: 'latest', locale },
|
||||||
props: { ...releaseNotes[0] },
|
props: { ...releaseNotes[0] },
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const release = Astro.props;
|
const release = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
---
|
---
|
||||||
import { Modal, ModalBody, ModalHeader } from "free-astro-components";
|
import { Modal, ModalBody, ModalHeader } from 'free-astro-components'
|
||||||
import { ArrowUp } from "lucide-astro";
|
import { ArrowUp } from 'lucide-astro'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import ReleaseNoteItem from "~/components/ReleaseNoteItem.astro";
|
import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import {
|
import {
|
||||||
releaseNotes as releaseNotesData,
|
releaseNotes as releaseNotesData,
|
||||||
releaseNotesTwilight,
|
releaseNotesTwilight,
|
||||||
} from "~/release-notes";
|
} from '~/release-notes'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { releaseNotes },
|
routes: { releaseNotes },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.releaseNotes.title}>
|
<Layout title={layout.releaseNotes.title}>
|
||||||
|
@ -33,7 +33,7 @@ const {
|
||||||
class="text-base opacity-55"
|
class="text-base opacity-55"
|
||||||
set:html={releaseNotes.topSection.description.replaceAll(
|
set:html={releaseNotes.topSection.description.replaceAll(
|
||||||
'{latestVersion}',
|
'{latestVersion}',
|
||||||
releaseNotesData[0].version
|
releaseNotesData[0].version,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Features from "~/components/Features.astro";
|
import Features from '~/components/Features.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { welcome },
|
routes: { welcome },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.welcome.title} description={layout.welcome.description}>
|
<Layout title={layout.welcome.title} description={layout.welcome.description}>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from "lucide-astro";
|
import { ArrowRight } from 'lucide-astro'
|
||||||
import Button from "~/components/Button.astro";
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro";
|
import Description from '~/components/Description.astro'
|
||||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||||
import Layout from "~/layouts/Layout.astro";
|
import Layout from '~/layouts/Layout.astro'
|
||||||
|
|
||||||
import whatsNewVideo from "~/assets/whats-new.mp4";
|
import whatsNewVideo from '~/assets/whats-new.mp4'
|
||||||
import Video from "~/components/Video.astro";
|
import Video from '~/components/Video.astro'
|
||||||
import { releaseNotes } from "~/release-notes";
|
import { releaseNotes } from '~/release-notes'
|
||||||
import whatsNewText from "~/release-notes/whats-new.json";
|
import whatsNewText from '~/release-notes/whats-new.json'
|
||||||
import { getLocale, getUI } from "~/utils/i18n";
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n";
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const latestVersion = releaseNotes[0];
|
const latestVersion = releaseNotes[0]
|
||||||
|
|
||||||
const locale = getLocale(Astro);
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { whatsNew },
|
routes: { whatsNew },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale);
|
} = getUI(locale)
|
||||||
|
|
||||||
// Just redirect to the release notes if we are in a patch version
|
// Just redirect to the release notes if we are in a patch version
|
||||||
if (
|
if (
|
||||||
latestVersion.version.split(".").length > 2 &&
|
latestVersion.version.split('.').length > 2 &&
|
||||||
whatsNewText[1] !== latestVersion.version
|
whatsNewText[1] !== latestVersion.version
|
||||||
) {
|
) {
|
||||||
return Astro.redirect(`/release-notes#${latestVersion.version}`);
|
return Astro.redirect(`/release-notes#${latestVersion.version}`)
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title={layout.whatsNew.title.replace(
|
title={layout.whatsNew.title.replace(
|
||||||
'{latestVersion.version}',
|
'{latestVersion.version}',
|
||||||
latestVersion.version
|
latestVersion.version,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<main
|
<main
|
||||||
|
@ -45,7 +45,7 @@ if (
|
||||||
>{
|
>{
|
||||||
whatsNew.title.replace(
|
whatsNew.title.replace(
|
||||||
'{latestVersion.version}',
|
'{latestVersion.version}',
|
||||||
latestVersion.version
|
latestVersion.version,
|
||||||
)
|
)
|
||||||
}</Description
|
}</Description
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,25 +3,29 @@
|
||||||
* Returns a mapping from filename to checksum.
|
* Returns a mapping from filename to checksum.
|
||||||
*/
|
*/
|
||||||
export async function getChecksums() {
|
export async function getChecksums() {
|
||||||
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
|
const res = await fetch(
|
||||||
headers: {
|
'https://api.github.com/repos/zen-browser/desktop/releases/latest',
|
||||||
'Accept': 'application/vnd.github+json',
|
{
|
||||||
'X-GitHub-Api-Version': '2022-11-28',
|
headers: {
|
||||||
'User-Agent': 'zen-browser-checksum-fetcher',
|
Accept: 'application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
'User-Agent': 'zen-browser-checksum-fetcher',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
)
|
||||||
if (!res.ok) throw new Error('Failed to fetch GitHub release: ' + res.statusText);
|
if (!res.ok)
|
||||||
const data = await res.json();
|
throw new Error('Failed to fetch GitHub release: ' + res.statusText)
|
||||||
const body = data.body as string;
|
const data = await res.json()
|
||||||
|
const body = data.body as string
|
||||||
|
|
||||||
// Extract the checksum block
|
// Extract the checksum block
|
||||||
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/);
|
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
|
||||||
const checksums: Record<string, string> = {};
|
const checksums: Record<string, string> = {}
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
match[1].split('\n').forEach(line => {
|
match[1].split('\n').forEach((line) => {
|
||||||
const [hash, filename] = line.trim().split(/\s+/, 2);
|
const [hash, filename] = line.trim().split(/\s+/, 2)
|
||||||
if (hash && filename) checksums[filename] = hash;
|
if (hash && filename) checksums[filename] = hash
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return checksums;
|
return checksums
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +1,85 @@
|
||||||
import type { GetStaticPaths } from "astro";
|
import type { GetStaticPaths } from 'astro'
|
||||||
import { CONSTANT } from "~/constants";
|
import { CONSTANT } from '~/constants'
|
||||||
import UI_EN from "~/i18n/en/translation.json";
|
import UI_EN from '~/i18n/en/translation.json'
|
||||||
|
|
||||||
export type Locale = (typeof locales)[number];
|
export type Locale = (typeof locales)[number]
|
||||||
|
|
||||||
export const getPath = (locale?: Locale) => (path: string) => {
|
export const getPath = (locale?: Locale) => (path: string) => {
|
||||||
if (locale && !path.startsWith(`/${locale}`)) {
|
if (locale && !path.startsWith(`/${locale}`)) {
|
||||||
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`;
|
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
|
||||||
}
|
}
|
||||||
return path;
|
return path
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getLocale = (Astro: any) => {
|
export const getLocale = (Astro: any) => {
|
||||||
if (Astro.params.locale) {
|
if (Astro.params.locale) {
|
||||||
return Astro.params.locale as 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(
|
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 => {
|
export const getUI = (locale?: Locale | string): UI => {
|
||||||
const validLocale = locales.includes(locale as Locale)
|
const validLocale = locales.includes(locale as Locale)
|
||||||
? locale
|
? locale
|
||||||
: CONSTANT.I18N.DEFAULT_LOCALE;
|
: CONSTANT.I18N.DEFAULT_LOCALE
|
||||||
const defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE];
|
const defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE]
|
||||||
const localeUI = ui[validLocale as Locale];
|
const localeUI = ui[validLocale as Locale]
|
||||||
|
|
||||||
function deepMerge<T>(defaultObj: T, overrideObj: Partial<T>): T {
|
function deepMerge<T>(defaultObj: T, overrideObj: Partial<T>): T {
|
||||||
if (typeof defaultObj !== "object" || defaultObj === null)
|
if (typeof defaultObj !== 'object' || defaultObj === null)
|
||||||
return (overrideObj ?? defaultObj) as T;
|
return (overrideObj ?? defaultObj) as T
|
||||||
if (typeof overrideObj !== "object" || overrideObj === null)
|
if (typeof overrideObj !== 'object' || overrideObj === null)
|
||||||
return (overrideObj ?? defaultObj) as T;
|
return (overrideObj ?? defaultObj) as T
|
||||||
const result: any = Array.isArray(defaultObj)
|
const result: any = Array.isArray(defaultObj)
|
||||||
? [...defaultObj]
|
? [...defaultObj]
|
||||||
: { ...defaultObj };
|
: { ...defaultObj }
|
||||||
for (const key in defaultObj) {
|
for (const key in defaultObj) {
|
||||||
if (Object.prototype.hasOwnProperty.call(defaultObj, key)) {
|
if (Object.prototype.hasOwnProperty.call(defaultObj, key)) {
|
||||||
result[key] = deepMerge(
|
result[key] = deepMerge(
|
||||||
(defaultObj as any)[key],
|
(defaultObj as any)[key],
|
||||||
(overrideObj as any)?.[key],
|
(overrideObj as any)?.[key],
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const key in overrideObj) {
|
for (const key in overrideObj) {
|
||||||
if (!(key in defaultObj)) {
|
if (!(key in defaultObj)) {
|
||||||
result[key] = (overrideObj as any)[key];
|
result[key] = (overrideObj as any)[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result as T;
|
return result as T
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepMerge<UI>(defaultUI, localeUI);
|
return deepMerge<UI>(defaultUI, localeUI)
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getStaticPaths = (() => {
|
export const getStaticPaths = (() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
params: { locale: undefined },
|
params: { locale: undefined },
|
||||||
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
|
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
|
||||||
},
|
},
|
||||||
...CONSTANT.I18N.LOCALES.filter(
|
...CONSTANT.I18N.LOCALES.filter(
|
||||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
||||||
).map(({ value }) => ({
|
).map(({ value }) => ({
|
||||||
params: { locale: value },
|
params: { locale: value },
|
||||||
props: {
|
props: {
|
||||||
locale: value,
|
locale: value,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
];
|
]
|
||||||
}) satisfies GetStaticPaths;
|
}) satisfies GetStaticPaths
|
||||||
|
|
||||||
export const getLocales = () => {
|
export const getLocales = () => {
|
||||||
return [...locales, ...otherLocales];
|
return [...locales, ...otherLocales]
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "preact",
|
"jsxImportSource": "preact",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue