feat: use next-intl (#1026)

* feat: use next-intl

* feat: set next-intl localePrefix to never

* chore: add next-intl

* chore: fix lock file

* feat: use [locale]

* fix: the custom rewrite conflict with next-intl rewrite in middleware

* fix: getTranslations function name

* fix: remove comma in translation key

* fix: next-intl dev error

* feat: update tran text

* feat: transaction for about page

* feat: i18n for server components

* feat: i18n for client components

* feat: i18n for client components and provider

* feat: i18n for language switch

* feat: remove i18next

* feat: custom intl error, ignore MISSING_MESSAGE error

* feat: use useLocale

* fix: unstable_setRequestLocale for middleware error
This commit is contained in:
DIYgod 2023-11-28 01:34:49 +08:00 committed by GitHub
parent b311d6a975
commit 91aa185d1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
185 changed files with 2858 additions and 3517 deletions

View File

@ -1,7 +1,7 @@
[
{
"name": "xLog",
"description": "Let's talk about xLog, whether to praise or criticize it.",
"description": "Let's talk about xLog, whether to praise or criticize it",
"includeTags": ["xLog"],
"includeKeywords": ["xLog"],
"excludeKeywords": ["同步更新至 xLog"]
@ -29,13 +29,13 @@
},
{
"name": "AI",
"description": "The world is undergoing changes, and we are at the center of it.",
"description": "The world is undergoing changes, and we are at the center of it",
"includeTags": ["AI"],
"includeKeywords": ["GPT", "Midjourney", "AIGC", " AI ", "OpenAI"]
},
{
"name": "Journal",
"description": "Discover new perspectives and insights in others' weekly, monthly, or yearly journal.",
"description": "Discover new perspectives and insights in others' weekly, monthly, or yearly journal",
"includeTags": ["Journal", "日记"],
"includeKeywords": [
"Review",
@ -49,7 +49,7 @@
},
{
"name": "Fiction",
"description": "Experience a world of imagination through these fiction stories.",
"description": "Experience a world of imagination through these fiction stories",
"includeTags": ["Fiction", "小说"],
"notes": [
"55405-3",

View File

@ -5,105 +5,110 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
})
const execSync = require("child_process").execSync
const withNextIntl = require("next-intl/plugin")()
const lastCommitCommand = "git rev-parse HEAD"
module.exports = withBundleAnalyzer({
output: "standalone",
env: {
APP_DESCRIPTION: pkg.description,
},
experimental: {
scrollRestoration: true,
serverComponentsExternalPackages: ["rehype-react"],
},
productionBrowserSourceMaps: true,
module.exports = withBundleAnalyzer(
withNextIntl({
output: "standalone",
env: {
APP_DESCRIPTION: pkg.description,
},
experimental: {
scrollRestoration: true,
serverComponentsExternalPackages: ["rehype-react"],
},
productionBrowserSourceMaps: true,
webpack(config) {
config.resolve.fallback = { fs: false } // polyfill node-id3
webpack(config) {
config.resolve.fallback = { fs: false } // polyfill node-id3
// https://github.com/WalletConnect/walletconnect-monorepo/blob/7716e164281c2f531145d682c3658f761fa0a823/providers/universal-provider/src/utils/deepLinks.ts#L39
// @walletconnect/universal-provider imports react-native conditionally.
// Since this is a NextJS app, simply mark it as external to avoid the webpack bundling warning.
config.externals.push("react-native")
// https://github.com/WalletConnect/walletconnect-monorepo/blob/7716e164281c2f531145d682c3658f761fa0a823/providers/universal-provider/src/utils/deepLinks.ts#L39
// @walletconnect/universal-provider imports react-native conditionally.
// Since this is a NextJS app, simply mark it as external to avoid the webpack bundling warning.
config.externals.push("react-native")
// https://github.com/WalletConnect/walletconnect-monorepo/issues/1908#issuecomment-1487801131
config.externals.push("pino-pretty", "lokijs", "encoding")
// https://github.com/WalletConnect/walletconnect-monorepo/issues/1908#issuecomment-1487801131
config.externals.push("pino-pretty", "lokijs", "encoding")
// https://github.com/WalletConnect/walletconnect-utils/blob/b7d7dc003c25dd33ef74c2fac483140f71a51d86/jsonrpc/http-connection/src/http.ts#L2
// `@walletconnect/jsonrpc-http-connection` imports `cross-fetch` to support fetch in Node.js. It's unnecessary for Next.JS app.
config.resolve.alias["cross-fetch"] = require.resolve(
"next/dist/build/polyfills/fetch/index.js",
)
// https://github.com/WalletConnect/walletconnect-utils/blob/b7d7dc003c25dd33ef74c2fac483140f71a51d86/jsonrpc/http-connection/src/http.ts#L2
// `@walletconnect/jsonrpc-http-connection` imports `cross-fetch` to support fetch in Node.js. It's unnecessary for Next.JS app.
config.resolve.alias["cross-fetch"] = require.resolve(
"next/dist/build/polyfills/fetch/index.js",
)
// https://github.com/kkomelin/isomorphic-dompurify/issues/54
// Fix isomorphic-dompurify in app router
config.externals = [...config.externals, "jsdom", "sharp"]
// https://github.com/kkomelin/isomorphic-dompurify/issues/54
// Fix isomorphic-dompurify in app router
config.externals = [...config.externals, "jsdom", "sharp"]
return config
},
return config
},
images: {
dangerouslyAllowSVG: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'none'; sandbox; style-src 'unsafe-inline';",
remotePatterns: [
{ hostname: "**" },
{ protocol: "https", hostname: "pbs.twimg.com" },
{ protocol: "https", hostname: "abs.twimg.com" },
],
},
images: {
dangerouslyAllowSVG: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'none'; sandbox; style-src 'unsafe-inline';",
remotePatterns: [
{ hostname: "**" },
{ protocol: "https", hostname: "pbs.twimg.com" },
{ protocol: "https", hostname: "abs.twimg.com" },
],
},
async generateBuildId() {
return execSync(lastCommitCommand).toString().trim()
},
async generateBuildId() {
return execSync(lastCommitCommand).toString().trim()
},
serverRuntimeConfig: {
ENV_REDIS_URL: process.env.REDIS_URL,
ENV_REDIS_EXPIRE: process.env.REDIS_EXPIRE,
ENV_REDIS_REFRESH: process.env.REDIS_REFRESH,
ENV_SIMPLEHASH_API_KEY: process.env.SIMPLEHASH_API_KEY,
ENV_OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ENV_ANONYMOUS_ACCOUNT_PRIVATEKEY: process.env.ANONYMOUS_ACCOUNT_PRIVATEKEY,
ENV_PORTFOLIO_GITHUB_TOKEN: process.env.PORTFOLIO_GITHUB_TOKEN,
},
serverRuntimeConfig: {
ENV_REDIS_URL: process.env.REDIS_URL,
ENV_REDIS_EXPIRE: process.env.REDIS_EXPIRE,
ENV_REDIS_REFRESH: process.env.REDIS_REFRESH,
ENV_SIMPLEHASH_API_KEY: process.env.SIMPLEHASH_API_KEY,
ENV_OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ENV_ANONYMOUS_ACCOUNT_PRIVATEKEY:
process.env.ANONYMOUS_ACCOUNT_PRIVATEKEY,
ENV_PORTFOLIO_GITHUB_TOKEN: process.env.PORTFOLIO_GITHUB_TOKEN,
},
async headers() {
return [
{
source: "/.well-known/apple-app-site-association",
headers: [
{
key: "Content-Type",
value: "application/json",
},
],
},
]
},
staticPageGenerationTimeout: 3600,
async headers() {
return [
{
source: "/.well-known/apple-app-site-association",
headers: [
{
key: "Content-Type",
value: "application/json",
},
],
},
]
},
staticPageGenerationTimeout: 3600,
async redirects() {
return [
{
source: "/sitemap.txt",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemap.xml.gz",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemap_index.xml",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemaps.xml",
destination: "/sitemap.xml",
permanent: true,
},
]
},
})
async redirects() {
return [
{
source: "/sitemap.txt",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemap.xml.gz",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemap_index.xml",
destination: "/sitemap.xml",
permanent: true,
},
{
source: "/sitemaps.xml",
destination: "/sitemap.xml",
permanent: true,
},
]
},
}),
)

View File

@ -81,8 +81,6 @@
"fast-deep-equal": "3.1.3",
"hast-util-from-html": "2.0.1",
"hast-util-to-html": "9.0.0",
"i18next": "23.7.6",
"i18next-resources-to-backend": "1.2.0",
"immer": "10.0.3",
"ioredis": "5.3.2",
"isomorphic-dompurify": "1.9.0",
@ -98,6 +96,7 @@
"mermaid": "10.6.1",
"nanoid": "5.0.3",
"next": "14.0.1",
"next-intl": "^3.1.2",
"next-themes": "0.2.1",
"node-id3": "0.2.6",
"open-graph-scraper": "^6.3.2",
@ -114,7 +113,6 @@
"react-dom": "18.2.0",
"react-hook-form": "7.48.2",
"react-hot-toast": "2.4.1",
"react-i18next": "13.5.0",
"react-parallax-tilt": "1.7.175",
"react-popper": "2.3.0",
"react-sortablejs": "6.1.4",

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
import { Metadata } from "next"
import { getTranslations } from "next-intl/server"
import { dehydrate, Hydrate } from "@tanstack/react-query"
import { HomeFeed } from "~/components/home/HomeFeed"
import ParticipateButton from "~/components/home/ParticipateButton"
import { APP_NAME } from "~/lib/env"
import { getTranslation } from "~/lib/i18n"
import getQueryClient from "~/lib/query-client"
import { prefetchGetFeed } from "~/queries/home.server"
import topics from "../../../../../../data/topics.json"
import topics from "../../../../../../../data/topics.json"
export function generateMetadata({
params,
@ -31,7 +31,7 @@ export default async function Topic({
topic: string
}
}) {
const { t } = await getTranslation("index")
const t = await getTranslations()
params.topic = decodeURIComponent(params.topic)
const info = topics.find((t) => t.name === params.topic)

View File

@ -1,3 +1,4 @@
import { getTranslations } from "next-intl/server"
import Link from "next/link"
import { memo } from "react"
@ -10,10 +11,9 @@ import { Button } from "~/components/ui/Button"
import { Image } from "~/components/ui/Image"
import { CSB_SCAN, GITHUB_LINK } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { getTranslation, Trans } from "~/lib/i18n"
async function Home() {
const { t, i18n } = await getTranslation("index")
const t = await getTranslations()
const EntranceButton = memo(function Button() {
return (
@ -118,32 +118,33 @@ async function Home() {
className={`text-feature text-feature-${feature.title.toLocaleLowerCase()} inline-block sm:mr-1`}
>
{t(feature.title)}
{t(".")}
{t("comma")}
</span>
))}
</h2>
<h3 className="mt-3 sm:mt-5 text-zinc-800 text-2xl sm:text-4xl font-light">
<Trans i18n={i18n} i18nKey="description" ns="index">
An{" "}
<a
target="_blank"
rel="nofollow noreferrer"
className="underline decoration-2 text-green-400 font-medium"
href={GITHUB_LINK}
>
open-source
</a>{" "}
creative community written on the{" "}
<a
target="_blank"
rel="nofollow noreferrer"
className="underline decoration-2 text-yellow-400 font-medium"
href={CSB_SCAN}
>
blockchain
</a>
.
</Trans>
{t.rich("description", {
opensourceLink: (chunks) => (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline decoration-2 text-green-400 font-medium"
href={GITHUB_LINK}
>
{chunks}
</a>
),
blockchainLink: (chunks) => (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline decoration-2 text-yellow-400 font-medium"
href={CSB_SCAN}
>
{chunks}
</a>
),
})}
</h3>
<div className="my-4 sm:my-12 flex items-center justify-center sm:flex-row flex-col">
<EntranceButton />
@ -193,23 +194,18 @@ async function Home() {
{t(`features.${feature.title}.subtitle`)}
</h4>
<p className="mt-6 text-zinc-500">
<Trans
i18n={i18n}
i18nKey={`features.${feature.title}.description`}
components={{
aown: (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href="https://github.com/Crossbell-Box/Crossbell-Contracts/wiki"
>
.
</a>
),
}}
ns="index"
/>
{t.rich(`features.${feature.title}.description`, {
aown: (chunks) => (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href="https://github.com/Crossbell-Box/Crossbell-Contracts/wiki"
>
{chunks}
</a>
),
})}
</p>
</div>
<ul className="pt-10 grid grid-cols-1 sm:grid-cols-3 gap-8">
@ -237,38 +233,35 @@ async function Home() {
</span>
</p>
<p className="text-base font-light mt-3 mb-4 leading-normal px-5 text-left">
<Trans
i18n={i18n}
i18nKey={`${item.title} text`}
components={{
strong: <strong className="font-bold" />,
axfm: (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href={`${getSiteLink({
subdomain: "xlog",
})}/xfm`}
>
.
</a>
),
aincentive: (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href={`${getSiteLink({
subdomain: "xlog",
})}/creator-incentive-plan`}
>
.
</a>
),
}}
ns="index"
/>
{t.rich(`${item.title} text`, {
strong: (chunks) => (
<strong className="font-bold">{chunks}</strong>
),
axfm: (chunks) => (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href={`${getSiteLink({
subdomain: "xlog",
})}/xfm`}
>
{chunks}
</a>
),
aincentive: (chunks) => (
<a
target="_blank"
rel="nofollow noreferrer"
className="underline"
href={`${getSiteLink({
subdomain: "xlog",
})}/creator-incentive-plan`}
>
{chunks}
</a>
),
})}
</p>
</li>
))}
@ -319,7 +312,7 @@ async function Home() {
<div className="my-10 text-zinc-700">
<p className="text-xl">
{t(
"xLog's open design allows it to integrate with many other open protocols and applications without friction.",
"xLog's open design allows it to integrate with many other open protocols and applications without friction",
)}
</p>
<ul className="mt-14 grid grid-cols-3 sm:grid-cols-5 gap-y-14 gap-x-2">

View File

@ -1,3 +1,4 @@
import { getLocale, getTranslations } from "next-intl/server"
import { notFound } from "next/navigation"
import { dehydrate, Hydrate } from "@tanstack/react-query"
@ -8,7 +9,6 @@ import PostModal from "~/components/home/PostModal"
import { PostFooter } from "~/components/site/PostFooter"
import PostMeta from "~/components/site/PostMeta"
import { SiteHeader } from "~/components/site/SiteHeader"
import { getTranslation } from "~/lib/i18n"
import { toCid } from "~/lib/ipfs-parser"
import { isOnlyContent } from "~/lib/is-only-content"
import getQueryClient from "~/lib/query-client"
@ -46,13 +46,13 @@ export default async function SiteModal({
const dehydratedState = dehydrate(queryClient)
const { i18n } = await getTranslation()
const { t } = await getTranslation("common")
const locale = await getLocale()
const t = await getTranslations()
let summary: string | undefined
if (!page.metadata.content.disableAISummary) {
summary = await getSummary({
cid: toCid(page.metadata?.uri || ""),
lang: i18n.resolvedLanguage,
lang: locale,
})
}
const onlyContent = isOnlyContent()

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { useEffect } from "react"
import { Virtuoso } from "react-virtuoso"
@ -8,13 +9,12 @@ import { CommentItem } from "~/components/common/CommentItem"
import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { UniLink } from "~/components/ui/UniLink"
import { getSiteLink } from "~/lib/helpers"
import { Trans, useTranslation } from "~/lib/i18n/client"
import { useGetCommentsBySite, useGetSite } from "~/queries/site"
export default function CommentsPage() {
const params = useParams()
const subdomain = params?.subdomain as string
const { t, i18n } = useTranslation("dashboard")
const t = useTranslations()
const site = useGetSite(subdomain)
@ -46,7 +46,7 @@ export default function CommentsPage() {
<div className="text-sm text-zinc-500 leading-relaxed">
<p>
{t(
"You can subscribe to comments through an RSS reader to receive timely reminders.",
"You can subscribe to comments through an RSS reader to receive timely reminders",
)}
</p>
<p>
@ -95,28 +95,18 @@ export default function CommentsPage() {
<div key={comment.transactionHash} className="mt-6">
<div>
{name}{" "}
<Trans
i18n={i18n}
i18nKey="comment on your"
values={{
type: t(type || "", {
ns: "common",
}),
toTitle,
}}
defaults="commented on your {{type}} <tolink>{{toTitle}}</tolink>"
components={{
tolink: (
<UniLink
href={`/api/redirection?characterId=${comment.characterId}&noteId=${comment.noteId}`}
target="_blank"
>
.
</UniLink>
),
}}
ns="dashboard"
/>
{t.rich("comment on your", {
tolink: (chunks) => (
<UniLink
href={`/api/redirection?characterId=${comment.characterId}&noteId=${comment.noteId}`}
target="_blank"
>
{chunks}
</UniLink>
),
type: t(type),
toTitle,
})}
:
</div>
<CommentItem
@ -137,7 +127,7 @@ export default function CommentsPage() {
}
const Loader = () => {
const { t } = useTranslation("common")
const t = useTranslations()
return (
<div
className="relative mt-4 w-full text-sm text-center py-4"

View File

@ -1,6 +1,7 @@
"use client"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useParams, useRouter, useSearchParams } from "next/navigation"
import { memo, useCallback, useEffect, useState } from "react"
import toast from "react-hot-toast"
@ -24,7 +25,6 @@ import { showConfetti } from "~/lib/confetti"
import { crossbell2Editor } from "~/lib/editor-converter"
import { CSB_SCAN } from "~/lib/env"
import { getTwitterShareUrl } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { getPageVisibility } from "~/lib/page-helpers"
import { delStorage, setStorage } from "~/lib/storage"
import { EditorValues, PageVisibilityEnum } from "~/lib/types"
@ -40,7 +40,7 @@ import { useGetSite } from "~/queries/site"
export default function PortfolioEditor() {
const router = useRouter()
const queryClient = useQueryClient()
const { t } = useTranslation("dashboard")
const t = useTranslations()
const params = useParams()
const subdomain = params?.subdomain as string
const searchParams = useSearchParams()
@ -285,8 +285,8 @@ export default function PortfolioEditor() {
visibility === PageVisibilityEnum.Draft
? `text-zinc-300`
: visibility === PageVisibilityEnum.Modified
? "text-orange-600"
: "text-green-600",
? "text-orange-600"
: "text-green-600",
)}
>
{t(visibility as string)}
@ -302,7 +302,7 @@ export default function PortfolioEditor() {
const EditorExtraProperties = memo(
({ updateValue }: { updateValue: (val: EditorValues) => void }) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
return (
<div className="w-full space-y-5">
@ -316,7 +316,7 @@ const EditorExtraProperties = memo(
<EditorPublishAt
updateValue={updateValue}
prompt={t(
"This portfolio will be accessible from this time. Leave blank to use the current time.",
"This portfolio will be accessible from this time Leave blank to use the current time",
)}
/>
</div>

View File

@ -1,6 +1,7 @@
"use client"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useParams, useRouter, useSearchParams } from "next/navigation"
import { memo, useCallback, useEffect, useState } from "react"
import toast from "react-hot-toast"
@ -32,7 +33,6 @@ import { getDefaultSlug } from "~/lib/default-slug"
import { crossbell2Editor } from "~/lib/editor-converter"
import { CSB_SCAN } from "~/lib/env"
import { getSiteLink, getTwitterShareUrl } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { getPageVisibility } from "~/lib/page-helpers"
import { delStorage, setStorage } from "~/lib/storage"
import {
@ -54,7 +54,7 @@ import { useGetSite } from "~/queries/site"
export default function PostEditor() {
const router = useRouter()
const queryClient = useQueryClient()
const { t } = useTranslation("dashboard")
const t = useTranslations()
const params = useParams()
const subdomain = params?.subdomain as string
const searchParams = useSearchParams()
@ -156,9 +156,7 @@ export default function PostEditor() {
// Replace all invalid chars
val.slug = val.slug.replace(/[^\w\-]/g, "-")
toast.error(
t(
"Slug can only contain letters, numbers, hyphens, and underscores.",
),
t("Slug can only contain letters, numbers, hyphens, and underscores"),
)
}
const newValues = { ...values, ...val }
@ -429,8 +427,8 @@ export default function PostEditor() {
visibility === PageVisibilityEnum.Draft
? `text-zinc-300`
: visibility === PageVisibilityEnum.Modified
? "text-orange-600"
: "text-green-600",
? "text-orange-600"
: "text-green-600",
)}
>
{t(visibility as string)}
@ -491,7 +489,7 @@ export default function PostEditor() {
})
}
className="h-12 ml-1 inline-flex items-center border-none text-3xl font-bold w-full focus:outline-none bg-white"
placeholder={t("Title goes here...") || ""}
placeholder={t("Title goes here") || ""}
/>
</div>
<div className="mt-5 flex-1 min-h-0">
@ -527,7 +525,7 @@ const EditorExtraProperties = memo(
characterId?: number
siteLink?: string
}) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
const { present } = useModalStack()
const openAdvancedOptions = () => {
@ -539,7 +537,7 @@ const EditorExtraProperties = memo(
<EditorPublishAt
updateValue={updateValue}
prompt={t(
`This ${type} will be accessible from this time. Leave blank to use the current time.`,
`This ${type} will be accessible from this time Leave blank to use the current time`,
)}
/>
</div>

View File

@ -1,6 +1,7 @@
"use client"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useParams, useRouter, useSearchParams } from "next/navigation"
import { memo, useCallback, useEffect, useState } from "react"
import toast from "react-hot-toast"
@ -22,7 +23,6 @@ import { showConfetti } from "~/lib/confetti"
import { crossbell2Editor } from "~/lib/editor-converter"
import { CSB_SCAN } from "~/lib/env"
import { getTwitterShareUrl } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { getPageVisibility } from "~/lib/page-helpers"
import { delStorage, setStorage } from "~/lib/storage"
import { EditorValues, PageVisibilityEnum } from "~/lib/types"
@ -38,7 +38,7 @@ import { useGetSite } from "~/queries/site"
export default function ShortEditor() {
const router = useRouter()
const queryClient = useQueryClient()
const { t } = useTranslation("dashboard")
const t = useTranslations()
const params = useParams()
const subdomain = params?.subdomain as string
const searchParams = useSearchParams()
@ -132,7 +132,7 @@ export default function ShortEditor() {
const savePage = async () => {
if (!values.images?.length) {
toast.error(t("Please upload at least one image."))
toast.error(t("Please upload at least one image"))
return
}
const baseValues = {
@ -287,8 +287,8 @@ export default function ShortEditor() {
visibility === PageVisibilityEnum.Draft
? `text-zinc-300`
: visibility === PageVisibilityEnum.Modified
? "text-orange-600"
: "text-green-600",
? "text-orange-600"
: "text-green-600",
)}
>
{t(visibility as string)}
@ -304,7 +304,7 @@ export default function ShortEditor() {
const EditorExtraProperties = memo(
({ updateValue }: { updateValue: (val: EditorValues) => void }) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
return (
<div className="w-full space-y-5">
@ -314,7 +314,7 @@ const EditorExtraProperties = memo(
<EditorPublishAt
updateValue={updateValue}
prompt={t(
"This short will be accessible from this time. Leave blank to use the current time.",
"This short will be accessible from this time Leave blank to use the current time",
)}
/>
</div>

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useEffect, useState } from "react"
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
@ -9,7 +10,6 @@ import { Image } from "~/components/ui/Image"
import { UniLink } from "~/components/ui/UniLink"
import { useDate } from "~/hooks/useDate"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { getStorage, setStorage } from "~/lib/storage"
import { cn } from "~/lib/utils"
import { useGetPagesBySite } from "~/queries/page"
@ -39,7 +39,7 @@ function SiteAvatar({ siteId }: { siteId: string }) {
export default function EventsPage() {
const date = useDate()
const { t } = useTranslation("dashboard")
const t = useTranslations()
const pages = useGetPagesBySite({
type: "post",
characterId: 50153,

View File

@ -1,6 +1,7 @@
"use client"
import type { NoteMetadata } from "crossbell"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
@ -10,7 +11,6 @@ import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { ImportPreview } from "~/components/dashboard/ImportPreview"
import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
import { useTranslation } from "~/lib/i18n/client"
import { readFiles } from "~/lib/read-files"
import { usePostNotes } from "~/queries/page"
import { useGetSite } from "~/queries/site"
@ -19,7 +19,7 @@ export default function ImportMarkdownPage() {
const params = useParams()
const subdomain = params?.subdomain as string
const site = useGetSite(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const form = useForm({
defaultValues: {
type: "post",
@ -81,7 +81,7 @@ export default function ImportMarkdownPage() {
accept=".md"
multiple={true}
help={t(
"Please select .md files, multiple files are supported, and front matter is supported.",
"Please select md files, multiple files are supported, and front matter is supported",
)}
{...form.register("files", {})}
/>

View File

@ -1,6 +1,7 @@
"use client"
import type { NoteMetadata } from "crossbell"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { useEffect } from "react"
import { useForm } from "react-hook-form"
@ -9,7 +10,6 @@ import { toast } from "react-hot-toast"
import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { ImportPreview } from "~/components/dashboard/ImportPreview"
import { Button } from "~/components/ui/Button"
import { useTranslation } from "~/lib/i18n/client"
import { useCheckMirror, useGetMirrorXyz, usePostNotes } from "~/queries/page"
import { useGetSite } from "~/queries/site"
@ -17,7 +17,7 @@ export default function ImportMirrorPage() {
const params = useParams()
const subdomain = params?.subdomain as string
const site = useGetSite(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const form = useForm({
defaultValues: {
type: "post",
@ -62,13 +62,13 @@ export default function ImportMirrorPage() {
}, [postNotes.isSuccess, form])
return (
<DashboardMain title="Import from Mirror.xyz">
<DashboardMain title="Import from Mirrorxyz">
{checkMirror?.data ? (
<form onSubmit={handleSubmit}>
<div className="min-w-[270px] max-w-screen-lg flex flex-col space-y-4">
<div>
<div className="form-label">
{t("Preview your Mirror.xyz entries")}
{t("Preview your Mirrorxyz entries")}
</div>
{notes?.length ? (
notes?.map((note: NoteMetadata) => (

View File

@ -1,15 +1,15 @@
"use client"
import { useTranslations } from "next-intl"
import { usePathname } from "next/navigation"
import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { Image } from "~/components/ui/Image"
import { UniLink } from "~/components/ui/UniLink"
import { useTranslation } from "~/lib/i18n/client"
export default function ImportPage() {
const pathname = usePathname()
const { t } = useTranslation("dashboard")
const t = useTranslations()
const options = [
{

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import {
useParams,
usePathname,
@ -7,7 +8,6 @@ import {
type ReadonlyURLSearchParams,
} from "next/navigation"
import React, { useEffect } from "react"
import { useTranslation } from "react-i18next"
import { useAccountState, useConnectModal } from "@crossbell/connect-kit"
import {
@ -48,7 +48,7 @@ export default function DashboardLayout({
const connectModal = useConnectModal()
const [ready, setReady] = React.useState(false)
const [hasPermission, setHasPermission] = React.useState(false)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const isMobileLayout = useIsMobileLayout()
const pathname = usePathname()
@ -269,8 +269,8 @@ export default function DashboardLayout({
active
? `bg-white font-medium text-accent drop-shadow-sm`
: link.href || link.onClick
? "hover:bg-slate-200 hover:bg-opacity-50"
: "opacity-80 cursor-default",
? "hover:bg-slate-200 hover:bg-opacity-50"
: "opacity-80 cursor-default",
)}
onClick={link.onClick}
>
@ -378,8 +378,8 @@ export default function DashboardLayout({
active
? `bg-white font-medium text-accent drop-shadow-sm`
: link.href || link.onClick
? "hover:bg-slate-200 hover:bg-opacity-50"
: "opacity-80 cursor-default",
? "hover:bg-slate-200 hover:bg-opacity-50"
: "opacity-80 cursor-default",
)}
onClick={link.onClick}
>

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
@ -15,7 +16,6 @@ import {
TWITTER_LINK,
} from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { Trans, useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
import { useGetShowcase } from "~/queries/home"
import { useGetPagesBySite } from "~/queries/page"
@ -30,7 +30,7 @@ export default function SubdomainIndex() {
characterId,
})
const date = useDate()
const { t, i18n } = useTranslation("dashboard")
const t = useTranslations()
const tips = useGetTips({
toCharacterId: characterId,
limit: 1000,
@ -133,15 +133,9 @@ export default function SubdomainIndex() {
))}
</div>
<div className="prose p-6 bg-slate-50 rounded-lg relative">
<Trans
i18n={i18n}
i18nKey="hello.welcome"
defaults="<p>👋 Hello there,</p><p>Welcome to use xLog!</p><p>Here are some useful links to get started:</p>"
components={{
p: <p />,
}}
ns="dashboard"
/>
{t.rich("hello.welcome", {
p: (chunks) => <p>{chunks}</p>,
})}
<ul>
<li>
<UniLink
@ -163,15 +157,9 @@ export default function SubdomainIndex() {
</UniLink>
</li>
</ul>
<Trans
i18n={i18n}
i18nKey="hello.community"
defaults="<p>Join the community to meet friends or build xLog together:</p>"
components={{
p: <p />,
}}
ns="dashboard"
/>
{t.rich("hello.tips", {
p: (chunks) => <p>{chunks}</p>,
})}
<ul
style={{
marginBottom: 0,

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { FC, useEffect, useRef, useState } from "react"
import toast from "react-hot-toast"
@ -10,7 +11,6 @@ import { Button } from "~/components/ui/Button"
import { FieldLabel } from "~/components/ui/FieldLabel"
import { useGetState } from "~/hooks/useGetState"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { useGetSite, useUpdateSite } from "~/queries/site"
export default function SettingsCSSPage() {
@ -19,7 +19,7 @@ export default function SettingsCSSPage() {
const updateSite = useUpdateSite()
const site = useGetSite(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const [css, setCss] = useState("")
const handleSubmit = (e: any) => {
e.preventDefault()
@ -55,12 +55,12 @@ export default function SettingsCSSPage() {
<p className="text-zinc-800 text-sm font-bold">{t("Tips")}:</p>
<p>
{t(
"Scope: These styles will be applied to your entire blog, including this dashboard.",
"Scope: These styles will be applied to your entire blog, including this dashboard",
)}
</p>
<p>
{t(
"Using a browser plugin that can modify page styles, such as Stylebot, can help with debugging.",
"Using a browser plugin that can modify page styles, such as Stylebot, can help with debugging",
)}
</p>
<p>
@ -127,7 +127,7 @@ const PreviewButton: FC<{
subdomain: string
custom_domain?: string
}> = ({ css, subdomain, custom_domain }) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
const datasetRef = useRef({
previewWindowOrigin: "",

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams, useRouter } from "next/navigation"
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
@ -18,7 +19,6 @@ import { UniLink } from "~/components/ui/UniLink"
import { useUserRole } from "~/hooks/useUserRole"
import { OUR_DOMAIN } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { checkDomain } from "~/models/site.model"
import { useGetSite, useUpdateHandle, useUpdateSite } from "~/queries/site"
@ -30,7 +30,7 @@ export default function SettingsDomainsPage() {
const updateHandle = useUpdateHandle()
const site = useGetSite(subdomain)
const userRole = useUserRole(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const router = useRouter()
const isEmailAccount = useAccountState(
@ -194,8 +194,8 @@ export default function SettingsDomainsPage() {
>
{t(
subdomainCheckResult.data
? "Subdomain Available."
: subdomainCheckResult.error || "Subdomain Unavailable.",
? "Subdomain Available"
: subdomainCheckResult.error || "Subdomain Unavailable",
)}
</span>
)}
@ -274,12 +274,12 @@ export default function SettingsDomainsPage() {
<span>{t("DNS Checking")}...</span>
) : domainCheckResult.data ? (
<span className="text-green-600">
{t("DNS check passed.")}
{t("DNS check passed")}
</span>
) : (
<span>
<span className="text-red-600">
{t("DNS check failed.")}
{t("DNS check failed")}
</span>
<Button
className="ml-4 font-media"

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { useEffect, useState } from "react"
import { Controller, useForm } from "react-hook-form"
@ -10,7 +11,6 @@ import { Button } from "~/components/ui/Button"
import { ImageUploader } from "~/components/ui/ImageUploader"
import { Input } from "~/components/ui/Input"
import { UniLink } from "~/components/ui/UniLink"
import { Trans, useTranslation } from "~/lib/i18n/client"
import { toIPFS } from "~/lib/ipfs-parser"
import { useGetSite, useUpdateSite } from "~/queries/site"
@ -20,7 +20,7 @@ export default function SiteSettingsGeneralPage() {
const updateSite = useUpdateSite()
const site = useGetSite(subdomain)
const { t, i18n } = useTranslation("dashboard")
const t = useTranslations()
const form = useForm({
defaultValues: {
@ -157,7 +157,7 @@ export default function SiteSettingsGeneralPage() {
)}
/>
<div className="text-xs text-gray-400 mt-1">
{t("Supports both pictures and videos.")}
{t("Supports both pictures and videos")}
</div>
</div>
<div className="mt-5">
@ -175,7 +175,7 @@ export default function SiteSettingsGeneralPage() {
placeholder={form.getValues("name")}
{...form.register("site_name")}
help={t(
"It will appear in the page title and site header, using the character name by default.",
"It will appear in the page title and site header, using the character name by default",
)}
/>
</div>
@ -199,21 +199,16 @@ export default function SiteSettingsGeneralPage() {
label="Google Analytics"
help={
<p>
<Trans
i18n={i18n}
i18nKey="Integrate Google Analytics"
ns="dashboard"
>
Integrate Google Analytics into your site. You can follow the
instructions{" "}
<UniLink
className="underline"
href="https://support.google.com/analytics/answer/9539598"
>
here
</UniLink>{" "}
to find your Measurement ID.
</Trans>
{t.rich("Integrate Google Analytics", {
link: (chunks) => (
<UniLink
className="underline"
href="https://support.google.com/analytics/answer/9539598"
>
{chunks}
</UniLink>
),
})}
</p>
}
/>
@ -227,16 +222,16 @@ export default function SiteSettingsGeneralPage() {
label="Umami Cloud Analytics"
help={
<p>
<Trans
i18nKey="Integrate Umami Cloud Analytics (set url)"
ns="dashboard"
>
Leave blank to use{" "}
<UniLink className="underline" href="https://cloud.umami.is">
the Umami Cloud
</UniLink>
.
</Trans>
{t.rich("Integrate Umami Cloud Analytics (set url)", {
link: (chunks) => (
<UniLink
className="underline"
href="https://cloud.umami.is"
>
{chunks}
</UniLink>
),
})}
</p>
}
/>
@ -247,21 +242,16 @@ export default function SiteSettingsGeneralPage() {
placeholder="xxxxxxxx-xxxx-..."
help={
<p>
<Trans
i18n={i18n}
i18nKey="Integrate Umami Cloud Analytics"
ns="dashboard"
>
Integrate Umami Cloud Analytics into your site. You can follow
the instructions{" "}
<UniLink
className="underline"
href="https://umami.is/docs/collect-data"
>
here
</UniLink>{" "}
to find your Website ID.
</Trans>
{t.rich("Integrate Umami Cloud Analytics", {
link: (chunks) => (
<UniLink
className="underline"
href="https://umami.is/docs/collect-data"
>
{chunks}
</UniLink>
),
})}
</p>
}
/>

View File

@ -2,6 +2,7 @@
import equal from "fast-deep-equal"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { ChangeEvent, FormEvent, useEffect, useMemo, useState } from "react"
import toast from "react-hot-toast"
@ -10,7 +11,6 @@ import { ReactSortable } from "react-sortablejs"
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
import { useTranslation } from "~/lib/i18n/client"
import { SiteNavigationItem } from "~/lib/types"
import { useGetSite, useUpdateSite } from "~/queries/site"
@ -27,7 +27,7 @@ const SortableNavigationItem = ({
updateItem: UpdateItem
removeItem: RemoveItem
}) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0">
<div>
@ -74,7 +74,7 @@ export default function SiteSettingsNavigationPage() {
const updateSite = useUpdateSite()
const site = useGetSite(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const [items, setItems] = useState<SiteNavigationItem[]>([])

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { useEffect, useState } from "react"
import toast from "react-hot-toast"
@ -17,7 +18,6 @@ import { Input } from "~/components/ui/Input"
import { UniLink } from "~/components/ui/UniLink"
import { useUserRole } from "~/hooks/useUserRole"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import {
useAddOperator,
useGetOperators,
@ -38,7 +38,7 @@ const SortableNavigationItem = ({
isLoading: boolean
disabled?: boolean
}) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0 items-center">
<div className="text-sm space-y-4">
@ -82,7 +82,7 @@ export default function SettingsOperatorPage() {
)
const upgradeAccountModal = useUpgradeEmailAccountModal()
const userRole = useUserRole(subdomain)
const { t } = useTranslation("dashboard")
const t = useTranslations()
const [items, setItems] = useState<string[]>([])
@ -215,7 +215,7 @@ export default function SettingsOperatorPage() {
{!isEmailAccount && userRole.data !== "operator" && (
<span>
{t(
"Operators have permissions to enter your dashboard, change your settings(excluding xLog subdomain) and post, modify, delete contents on your site.",
"Operators have permissions to enter your dashboard, change your settings(excluding xLog subdomain) and post, modify, delete contents on your site",
)}
</span>
)}

View File

@ -2,6 +2,7 @@
import equal from "fast-deep-equal"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { ChangeEvent, FormEvent, useEffect, useMemo, useState } from "react"
import toast from "react-hot-toast"
@ -12,7 +13,6 @@ import { Platform, PlatformsSyncMap } from "~/components/site/Platform"
import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
import { UniLink } from "~/components/ui/UniLink"
import { Trans, useTranslation } from "~/lib/i18n/client"
import { useGetSite, useUpdateSite } from "~/queries/site"
type Item = {
@ -36,7 +36,7 @@ const SortableNavigationItem = ({
updateItem: UpdateItem
removeItem: RemoveItem
}) => {
const { t } = useTranslation("dashboard")
const t = useTranslations()
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0">
<div>
@ -86,7 +86,7 @@ export default function SiteSettingsNavigationPage() {
const updateSite = useUpdateSite()
const site = useGetSite(subdomain)
const { t, i18n } = useTranslation("dashboard")
const t = useTranslations()
const [items, setItems] = useState<Item[]>([])
@ -169,18 +169,16 @@ export default function SiteSettingsNavigationPage() {
</p>
<p>
<span className="text-zinc-800">
<Trans i18n={i18n} ns="dashboard" i18nKey="social tips.p2">
We support{" "}
<UniLink
href="https://github.com/Crossbell-Box/xLog/blob/dev/src/components/site/Platform.tsx#L11"
className="underline"
>
these platforms
</UniLink>{" "}
with automatic display of logos and links, other platforms will
display a default logo. Please feel free to submit an issue or pr
to us to support more platforms.
</Trans>
{t.rich("social tips.p2", {
link: (chunks) => (
<UniLink
href="https://github.com/Crossbell-Box/xLog/blob/dev/src/components/site/Platform.tsx#L11"
className="underline"
>
(chunks)
</UniLink>
),
})}
</span>
</p>
</div>

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import {
@ -13,13 +14,11 @@ import { Button } from "~/components/ui/Button"
import { UniLink } from "~/components/ui/UniLink"
import { MIRA_LINK } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { useGetMiraBalance, useGetSite } from "~/queries/site"
export default function TokensPage() {
const params = useParams()
const { t } = useTranslation("dashboard")
const { t: indexT } = useTranslation("index")
const t = useTranslations()
const subdomain = params?.subdomain as string
const site = useGetSite(subdomain)
@ -38,11 +37,11 @@ export default function TokensPage() {
<>
<p>
{t(
"$MIRA is a valuable token in the Crossbell world, and can be easily exchanged on the Crosschain Bridge and Uniswap.",
"$MIRA is a valuable token in the Crossbell world, and can be easily exchanged on the Crosschain Bridge and Uniswap",
)}
</p>
<p>
{t("In the early stage, xLog will use $MIRA to motivate creators.")}
{t("In the early stage, xLog will use $MIRA to motivate creators")}
</p>
<p>{t("You can obtain $MIRA through the following ways:")}</p>
<ul className="ml-2">
@ -54,7 +53,7 @@ export default function TokensPage() {
subdomain: "xlog",
})}/creator-incentive-plan`}
>
{t("Creator incentive program.")}
{t("Creator incentive program")}
</UniLink>
</li>
<li>
@ -63,16 +62,16 @@ export default function TokensPage() {
className="underline"
href={`/dashboard/${subdomain}/events`}
>
{t("Participate in events.")}
{t("Participate in events")}
</UniLink>
</li>
<li>
3.{" "}
<UniLink className="underline" href={MIRA_LINK}>
{t("Swap from USDC.")}
{t("Swap from USDC")}
</UniLink>
</li>
<li>4. {t("Received tips and sponsorships from readers.")}</li>
<li>4. {t("Received tips and sponsorships from readers")}</li>
</ul>
</>
),
@ -96,7 +95,7 @@ export default function TokensPage() {
description: (
<>
{t(
"This is a token used for interaction on the Crossbell blockchain, which can be claimed for free from the faucet, so there's no need to worry about its balance.",
"This is a token used for interaction on the Crossbell blockchain, which can be claimed for free from the faucet, so there's no need to worry about its balance",
)}
</>
),
@ -114,7 +113,7 @@ export default function TokensPage() {
{
name: "XLOG",
description: t(
"$XLOG is related to xLog DAO, but it's too early now, please stay tuned.",
"$XLOG is related to xLog DAO, but it's too early now, please stay tuned",
),
balance: t("Stay tuned"),
},
@ -124,7 +123,7 @@ export default function TokensPage() {
<DashboardMain title="Tokens">
<div className="min-w-[270px] max-w-screen-lg flex flex-col space-y-8">
<div className="text-sm text-zinc-500 leading-relaxed">
{indexT("features.Earn.description")}
{t("features.Earn.description")}
</div>
{tokens.map((token) => {
return (

View File

@ -1,13 +1,14 @@
import { dir } from "i18next"
import { Metadata } from "next"
import { NextIntlClientProvider } from "next-intl"
import { getMessages, unstable_setRequestLocale } from "next-intl/server"
import { headers } from "next/headers"
import { notFound } from "next/navigation"
import Script from "next/script"
import { Toaster } from "react-hot-toast"
import { updateIndexerFetchOptions } from "@crossbell/indexer"
import { ColorSchemeScript, MantineProvider } from "@mantine/core"
import { getAcceptLang } from "~/lib/accept-lang"
import {
APP_DESCRIPTION,
APP_NAME,
@ -92,14 +93,22 @@ export const metadata: Metadata = {
},
}
export default function RootLayout({
export default async function RootLayout({
children,
modal,
params,
}: {
children: React.ReactNode
modal: React.ReactNode
params?: {
locale: string
}
}) {
const lang = getAcceptLang()
if (!params?.locale) {
return notFound()
}
// const lang = getAcceptLang()
const colorScheme = getColorScheme()
// For viewing statistics
@ -112,22 +121,27 @@ export default function RootLayout({
})
}
unstable_setRequestLocale(params.locale)
const messages = await getMessages()
return (
<html lang={lang} dir={dir(lang)} className={colorScheme}>
<html lang={params.locale} className={colorScheme}>
<head>
<ColorSchemeInjector />
<ColorSchemeScript />
</head>
<body>
<MantineProvider
theme={mantineTheme}
defaultColorScheme={mantineDefaultColorScheme}
>
<Providers lang={lang}>
{modal}
{children}
</Providers>
</MantineProvider>
<NextIntlClientProvider messages={messages}>
<MantineProvider
theme={mantineTheme}
defaultColorScheme={mantineDefaultColorScheme}
>
<Providers>
{modal}
{children}
</Providers>
</MantineProvider>
</NextIntlClientProvider>
<Toaster />
<Script
id="xlog-umami-analytics"

View File

@ -1,12 +1,12 @@
"use client"
import { useTranslations } from "next-intl"
import { useCallback, useEffect } from "react"
import { Image } from "~/components/ui/Image"
import { useTranslation } from "~/lib/i18n/client"
function OIAPage() {
const { t } = useTranslation("site")
const t = useTranslations()
useEffect(() => {
document.body.style.backgroundColor = "#fb9148"

View File

@ -1,5 +1,13 @@
"use client"
import {
IntlError,
IntlErrorCode,
NextIntlClientProvider,
useLocale,
useMessages,
useTimeZone,
} from "next-intl"
import { ThemeProvider } from "next-themes"
import { useState } from "react"
import { WagmiConfig } from "wagmi"
@ -22,7 +30,6 @@ import { APP_NAME, WALLET_CONNECT_V2_PROJECT_ID } from "~/lib/env"
import { filterNotificationCharacter } from "~/lib/filter-character"
import { toGateway } from "~/lib/ipfs-parser"
import { urlComposer } from "~/lib/url-composer"
import { LangProvider } from "~/providers/LangProvider"
const wagmiConfig = createWagmiConfig({
appName: APP_NAME,
@ -54,44 +61,51 @@ export const mantineDefaultColorScheme = mantineDefaultColorSchemeT as
| "light"
| "dark"
export default function Providers({
children,
lang,
}: {
children: React.ReactNode
lang: string
}) {
export default function Providers({ children }: { children: React.ReactNode }) {
useMobileLayout()
useNProgress()
useDarkModeListener()
const [queryClient] = useState(() => new QueryClient())
return (
<ThemeProvider
disableTransitionOnChange
storageKey={DARK_MODE_STORAGE_KEY}
attribute="class"
>
<WagmiConfig config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<ConnectKitProvider
ipfsLinkToHttpLink={toGateway}
urlComposer={urlComposer}
signInStrategy="simple"
ignoreWalletDisconnectEvent={true}
>
<LangProvider lang={lang}>
<ModalStackProvider>{children}</ModalStackProvider>
</LangProvider>
const messages = useMessages()
const locale = useLocale()
const timeZone = useTimeZone()
const onIntlError = (error: IntlError) => {
if (error.code !== IntlErrorCode.MISSING_MESSAGE) {
console.log(`Intl error`, error)
}
}
<NotificationModal
colorScheme={colorScheme}
filter={filterNotificationCharacter}
/>
</ConnectKitProvider>
</QueryClientProvider>
</WagmiConfig>
</ThemeProvider>
return (
<NextIntlClientProvider
onError={onIntlError}
locale={locale}
messages={messages}
timeZone={timeZone}
>
<ThemeProvider
disableTransitionOnChange
storageKey={DARK_MODE_STORAGE_KEY}
attribute="class"
>
<WagmiConfig config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<ConnectKitProvider
ipfsLinkToHttpLink={toGateway}
urlComposer={urlComposer}
signInStrategy="simple"
ignoreWalletDisconnectEvent={true}
>
<ModalStackProvider>{children}</ModalStackProvider>
<NotificationModal
colorScheme={colorScheme}
filter={filterNotificationCharacter}
/>
</ConnectKitProvider>
</QueryClientProvider>
</WagmiConfig>
</ThemeProvider>
</NextIntlClientProvider>
)
}

View File

@ -1,4 +1,5 @@
import { Metadata } from "next"
import { getLocale, getTranslations } from "next-intl/server"
import { notFound } from "next/navigation"
import serialize from "serialize-javascript"
@ -11,7 +12,6 @@ import { PostFooter } from "~/components/site/PostFooter"
import PostMeta from "~/components/site/PostMeta"
import { SITE_URL } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { getTranslation } from "~/lib/i18n"
import { toCid, toGateway } from "~/lib/ipfs-parser"
import { isInRN } from "~/lib/is-in-rn"
import { isOnlyContent } from "~/lib/is-only-content"
@ -132,13 +132,13 @@ export default async function SitePagePage({
}
}
const { i18n } = await getTranslation()
const { t } = await getTranslation("common")
const locale = await getLocale()
const t = await getTranslations()
let summary: string | undefined
if (!page.metadata.content.disableAISummary) {
summary = await getSummary({
cid: toCid(page.metadata?.uri || ""),
lang: i18n.resolvedLanguage,
lang: locale,
})
}

View File

@ -1,8 +1,8 @@
import { Metadata } from "next"
import { getTranslations } from "next-intl/server"
import { Image } from "~/components/ui/Image"
import { UniLink } from "~/components/ui/UniLink"
import { getTranslation } from "~/lib/i18n"
import getQueryClient from "~/lib/query-client"
import { fetchGetSite, getNFTs } from "~/queries/site.server"
@ -34,7 +34,7 @@ export default async function SiteNFTPage({
const queryClient = getQueryClient()
const site = await fetchGetSite(params.site, queryClient)
const { t } = await getTranslation("common")
const t = await getTranslations()
let nfts = (await getNFTs(site?.owner)) ?? {
nfts: [],

View File

@ -1,16 +1,17 @@
import { getTranslations } from "next-intl/server"
import { Image } from "~/components/ui/Image"
import { SITE_URL } from "~/lib/env"
import { getTranslation } from "~/lib/i18n"
export default async function NotFound() {
const { t } = await getTranslation("site")
const t = await getTranslations()
return (
<>
<article>
<div>
<h2 className="xlog-post-title text-xl font-bold page-title text-center">
{t("404 - Whoops, this page is gone.")}
{t("404 - Whoops, this page is gone")}
</h2>
</div>
<div className="mt-10 xlog-post-content prose">

View File

@ -1,9 +1,9 @@
"use client"
import { useTranslations } from "next-intl"
import dynamic from "next/dynamic"
import { useParams } from "next/navigation"
import { useTranslation } from "~/lib/i18n/client"
import { useGetPage } from "~/queries/page"
import { useGetSite } from "~/queries/site"
@ -28,13 +28,13 @@ export default function SitePreviewPage() {
useStat: true,
})
const { t } = useTranslation("site")
const t = useTranslations()
return (
<div className="max-w-screen-md mx-auto">
<div className="fixed top-0 left-0 w-full text-center text-red-500 bg-gray-100 py-2 opacity-80 text-sm z-10">
{t(
"This address is in local editing preview mode and cannot be viewed by the public.",
"This address is in local editing preview mode and cannot be viewed by the public",
)}
</div>
<article>

View File

@ -1,3 +1,4 @@
import { useTranslations } from "next-intl"
import { useState } from "react"
import { Indicator } from "@mantine/core"
@ -5,7 +6,6 @@ import { Indicator } from "@mantine/core"
import { AchievementModal } from "~/components/common/AchievementModal"
import { Image } from "~/components/ui/Image"
import { useDate } from "~/hooks/useDate"
import { useTranslation } from "~/lib/i18n/client"
import type { AchievementSection } from "~/models/site.model"
export const Badge = ({
@ -48,7 +48,7 @@ export const AchievementItem = ({
isOwner: boolean
}) => {
const date = useDate()
const { t } = useTranslation("common")
const t = useTranslations()
const achievement = group.items
.filter((item) => item.status === "MINTED")

View File

@ -1,3 +1,4 @@
import { useTranslations } from "next-intl"
import Tilt from "react-parallax-tilt"
import { Modal, Stepper } from "@mantine/core"
@ -7,7 +8,6 @@ import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
import { Button } from "~/components/ui/Button"
import { Image } from "~/components/ui/Image"
import { useDate } from "~/hooks/useDate"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
import type { AchievementSection } from "~/models/site.model"
import { useMintAchievement } from "~/queries/site"
@ -28,7 +28,7 @@ export const AchievementModal = ({
characterId?: number
}) => {
const date = useDate()
const { t } = useTranslation("common")
const t = useTranslations()
const achievement = group.items
.filter((item) => item.status === "MINTED")

View File

@ -1,10 +1,10 @@
"use client"
import { useTranslations } from "next-intl"
import { useEffect, useState } from "react"
import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
import { CSB_SCAN, SITE_URL } from "~/lib/env"
import { useTranslation } from "~/lib/i18n/client"
import { toGateway, toIPFS } from "~/lib/ipfs-parser"
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
@ -17,7 +17,7 @@ export const BlockchainInfo = ({
site?: ExpandedCharacter
page?: ExpandedNote
}) => {
const { t } = useTranslation("common")
const t = useTranslations()
const ipfs = (page ? page.metadata?.uri : site?.metadata?.uri) || ""
@ -83,7 +83,7 @@ export const BlockchainInfo = ({
<span className="ml-2">
{!!blockNumber &&
!!realBlockNumber &&
`(${t("Confirmed by {{blockNumber}} blocks", {
`(${t("Confirmed by {blockNumber} blocks", {
blockNumber:
realBlockNumber -
(page?.blockNumber || site?.blockNumber || 0),

View File

@ -1,10 +1,11 @@
import { useTranslations } from "next-intl"
import { FollowingButton } from "~/components/common/FollowingButton"
import { FollowingCount } from "~/components/common/FollowingCount"
import { Titles } from "~/components/common/Titles"
import { Avatar } from "~/components/ui/Avatar"
import { Skeleton } from "~/components/ui/Skeleton"
import { useDate } from "~/hooks/useDate"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
import { useGetCharacterCard } from "~/queries/site"
@ -26,7 +27,7 @@ export const CharacterCard = ({
style?: "flat" | "normal"
}) => {
const date = useDate()
const { t } = useTranslation("common")
const t = useTranslations()
const { data: site } = useGetCharacterCard({
siteId,

View File

@ -1,8 +1,8 @@
import { useTranslations } from "next-intl"
import React, { useCallback, useState } from "react"
import { Virtuoso } from "react-virtuoso"
import { Modal } from "~/components/ui/Modal"
import { useTranslation } from "~/lib/i18n/client"
import { ExpandedCharacter } from "~/lib/types"
import { Button } from "../ui/Button"
@ -26,7 +26,7 @@ export const CharacterList = ({
} | null)[]
title: string
}) => {
const { t } = useTranslation("common")
const t = useTranslations()
const flattenList = list?.reduce(
(acc, cur) => acc.concat(cur?.list || []),
[] as any[],
@ -76,7 +76,7 @@ export const CharacterList = ({
) : (
<div className="px-5 overflow-auto flex-1">
<div className="py-3 text-center text-zinc-300">
{t("No Content Yet.")}
{t("No Content Yet")}
</div>
</div>
)}
@ -91,7 +91,7 @@ export const CharacterList = ({
}
const Loader = () => {
const { t } = useTranslation("common")
const t = useTranslations()
return (
<div className="text-sm py-3 text-center" key={0}>
{t("Loading")} ...

View File

@ -1,12 +1,12 @@
"use client"
import { useTranslations } from "next-intl"
import { useState } from "react"
// TODO
import { Virtuoso } from "react-virtuoso"
import { CommentInput } from "~/components/common/CommentInput"
import { CommentItem } from "~/components/common/CommentItem"
import { useTranslation } from "~/lib/i18n/client"
import { ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
import { useGetComments } from "~/queries/page"
@ -24,7 +24,7 @@ const Comment = ({
characterId: page?.characterId,
noteId: page?.noteId,
})
const { t } = useTranslation("common")
const t = useTranslations()
const data = comments.data?.pages.filter(
(page) => (page?.list?.length ?? 0) > 0,
@ -99,7 +99,7 @@ const Comment = ({
}
const Loader = () => {
const { t } = useTranslation("common")
const t = useTranslations()
return (
<div
className="relative mt-4 w-full text-sm text-center py-4"

View File

@ -1,5 +1,6 @@
import type { CharacterEntity, NoteEntity } from "crossbell"
import { nanoid } from "nanoid"
import { useTranslations } from "next-intl"
import { useCallback, useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
@ -12,7 +13,6 @@ import { Avatar } from "~/components/ui/Avatar"
import { Button } from "~/components/ui/Button"
import CodeMirrorEditor from "~/components/ui/CodeMirror"
import { useUploadFile } from "~/hooks/useUploadFile"
import { useTranslation } from "~/lib/i18n/client"
import {
useAnonymousComment,
useCommentPage,
@ -44,7 +44,7 @@ export const CommentInput = ({
const account = useAccountState((s) => s.computed.account)
const commentPage = useCommentPage()
const updateComment = useUpdateComment()
const { t } = useTranslation("site")
const t = useTranslations()
const anonymousComment = useAnonymousComment()
const [anonymous, setAnonymous] = useState(false)

View File

@ -1,6 +1,7 @@
"use client"
import type { CharacterEntity, NoteEntity } from "crossbell"
import { useTranslations } from "next-intl"
import { useEffect, useState } from "react"
import { useAccountState } from "@crossbell/connect-kit"
@ -19,7 +20,6 @@ import { UniLink } from "~/components/ui/UniLink"
import { useDate } from "~/hooks/useDate"
import { CSB_SCAN } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
import { useDeletePage } from "~/queries/page"
@ -42,7 +42,7 @@ export const CommentItem = ({
const [editOpen, setEditOpen] = useState(false)
const [deleteConfirmModalOpen, setDeleteConfirmModalOpen] = useState(false)
const { t } = useTranslation("common")
const t = useTranslations()
const date = useDate()
const queryClient = useQueryClient()
const deletePage = useDeletePage()

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import React, { useEffect, useState } from "react"
import {
@ -33,7 +34,6 @@ import { Button, type Variant, type VariantColor } from "~/components/ui/Button"
import { Menu } from "~/components/ui/Menu"
import { SITE_URL } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
type HeaderLinkType = {
@ -99,7 +99,7 @@ export const ConnectButton = ({
const { isAllRead } = useNotifications()
const [InsufficientBalance, setInsufficientBalance] = useState<boolean>(false)
const { t } = useTranslation("common")
const t = useTranslations()
useEffect(() => {
if (balance) {

View File

@ -1,4 +1,4 @@
import { useTranslation } from "~/lib/i18n/client"
import { useTranslations } from "next-intl"
import { Button } from "../ui/Button"
import { Modal } from "../ui/Modal"
@ -14,7 +14,7 @@ export const DeleteConfirmationModal = ({
onConfirm: () => void
type?: string
}) => {
const { t } = useTranslation("common")
const t = useTranslations()
return (
<Modal open={open} setOpen={setOpen}>

View File

@ -1,5 +1,5 @@
import { useTranslations } from "next-intl"
import { useEffect, useRef } from "react"
import { useTranslation } from "react-i18next"
import { useIsDark } from "~/hooks/useDarkMode"
@ -26,7 +26,7 @@ export const EmojiPicker = ({
})
}, [])
const { t } = useTranslation("common")
const t = useTranslations()
return (
<div

View File

@ -1,12 +1,12 @@
"use client"
import { useTranslations } from "next-intl"
import { useState } from "react"
import { useAccountState } from "@crossbell/connect-kit"
import { useRefCallback } from "@crossbell/util-hooks"
import { Button } from "~/components/ui/Button"
import { useTranslation } from "~/lib/i18n/client"
import { getSubscriptionsFromList } from "~/models/site.model"
import { useSubscribeToSites } from "~/queries/site"
@ -22,7 +22,7 @@ export const FollowAllButton = ({
className?: string
}) => {
const subscribeToSites = useSubscribeToSites()
const { t } = useTranslation("index")
const t = useTranslations()
const account = useAccountState((s) => s.computed.account)
const [noMore, setNoMore] = useState(false)

View File

@ -1,5 +1,6 @@
"use client"
import { useTranslations } from "next-intl"
import { useEffect } from "react"
import { toast } from "react-hot-toast"
@ -7,7 +8,6 @@ import { Button } from "~/components/ui/Button"
import type { Variant } from "~/components/ui/Button"
import { UniLink } from "~/components/ui/UniLink"
import { SITE_URL } from "~/lib/env"
import { Trans, useTranslation } from "~/lib/i18n/client"
import { ExpandedCharacter } from "~/lib/types"
import { cn } from "~/lib/utils"
import {
@ -31,7 +31,7 @@ export const FollowingButton = ({
}) => {
const subscribeToSite = useSubscribeToSite()
const unsubscribeFromSite = useUnsubscribeFromSite()
const { t, i18n } = useTranslation("common")
const t = useTranslations()
const handleClickSubscribe = () => {
if (site?.characterId) {
@ -62,13 +62,13 @@ export const FollowingButton = ({
subscribeToSite.reset()
toast.success(
<span>
<Trans i18n={i18n} i18nKey="Successfully followed" ns="common">
Hey there! You&apos;re all set to{" "}
<UniLink className="underline" href={`${SITE_URL}/`}>
keep up with your followed blogger&apos;s latest buzz here
</UniLink>
.
</Trans>
{t.rich("Successfully followed", {
link: (chunks) => (
<UniLink className="underline" href={`${SITE_URL}/`}>
{chunks}
</UniLink>
),
})}
</span>,
{
duration: 5000,

View File

@ -1,10 +1,10 @@
"use client"
import { useTranslations } from "next-intl"
import { useState } from "react"
import { CharacterList } from "~/components/common/CharacterList"
import { Button } from "~/components/ui/Button"
import { useTranslation } from "~/lib/i18n/client"
import {
useGetSiteSubscriptions,
useGetSiteToSubscriptions,
@ -19,7 +19,7 @@ export const FollowingCount = ({
}) => {
let [isFollowListOpen, setIsFollowListOpen] = useState(false)
let [isToFollowListOpen, setIsToFollowListOpen] = useState(false)
const { t } = useTranslation("common")
const t = useTranslations()
const subscriptions = useGetSiteSubscriptions({
characterId,

View File

@ -1,11 +1,20 @@
"use client"
import { changeLanguage, useAvailableLanguages } from "~/lib/i18n/client"
import { useLocale } from "next-intl"
import { locales } from "~/i18n"
import { Menu } from "../ui/Menu"
export function LanguageSwitch() {
const languages = useAvailableLanguages()
const locale = useLocale()
const nameMap: Record<string, string> = {
en: "English",
zh: "简体中文",
"zh-TW": "繁體中文",
ja: "日本語",
}
return (
<Menu
@ -15,17 +24,18 @@ export function LanguageSwitch() {
}
dropdown={
<>
{languages.map((language, i) => (
{locales.map((lo, i) => (
<Menu.Item
key={i}
type="button"
onClick={() => {
changeLanguage(language.code)
document.cookie = `NEXT_LOCALE=${lo};`
window.location.reload()
}}
className="mx-auto"
>
<span>{language.name}</span>
{language.active && (
<span>{nameMap[lo]}</span>
{locale === lo && (
<span className="ml-2 icon-[mingcute--check-line]"></span>
)}
</Menu.Item>

View File

@ -1,8 +1,9 @@
"use client"
import { useTranslations } from "next-intl"
import { usePatronModal } from "~/components/common/PatronModal"
import { Button } from "~/components/ui/Button"
import { useTranslation } from "~/lib/i18n/client"
import { ExpandedCharacter } from "~/lib/types"
import { cn } from "~/lib/utils"
@ -17,7 +18,7 @@ export const PatronButton = ({
size?: "sm" | "xl"
loadingStatusChange?: (status: boolean) => void
}) => {
const { t } = useTranslation("common")
const t = useTranslations()
const presentPatronModal = usePatronModal()

Some files were not shown because too many files have changed in this diff Show More