diff --git a/src/components/CategoryArticles/CategoryArticles.tsx b/src/components/CategoryArticles/CategoryArticles.tsx
index c8ac5e04..e1c0c1a4 100644
--- a/src/components/CategoryArticles/CategoryArticles.tsx
+++ b/src/components/CategoryArticles/CategoryArticles.tsx
@@ -3,6 +3,7 @@ import { NextIntlClientProvider } from "next-intl"
import { useLocale } from "@/i18n/i18n"
import { listArticlesByCategory } from "@/lib/client"
import { CategoryArticlesInfiniteDynamic } from "./CategoryArticlesInfiniteDynamic"
+import { getTranslations } from "@/i18n/setTranslations"
export const CATEGORY_ARTICLES_PER_PAGE = 4
@@ -12,6 +13,7 @@ type CategoryArticlesProps = {
export async function CategoryArticles({ category }: CategoryArticlesProps) {
const locale = useLocale()
+ const translations = getTranslations()
const articles = await listArticlesByCategory({
locale: locale,
categorySlug: category,
@@ -22,8 +24,8 @@ export async function CategoryArticles({ category }: CategoryArticlesProps) {
return (
-
Search Category
-
Showing {articles.count} results for:
+
{translations.searchCategory}
+
{`${translations.showing} ${articles.count} ${translations.resultsFor}`}
"{category}"
diff --git a/src/components/CategoryArticles/CategoryArticlesInfinite.tsx b/src/components/CategoryArticles/CategoryArticlesInfinite.tsx
index 5ff2f792..fe571be7 100644
--- a/src/components/CategoryArticles/CategoryArticlesInfinite.tsx
+++ b/src/components/CategoryArticles/CategoryArticlesInfinite.tsx
@@ -4,6 +4,7 @@ import { useInfiniteQuery } from "@tanstack/react-query"
import { Button } from "@/components/ui/Button/Button"
import { ListArticlesByCategoryQuery } from "@/gql/graphql"
import { useLocale } from "@/i18n/i18n"
+import { useTranslations } from "@/i18n/useTranslations"
import { listArticlesByCategory } from "@/lib/client"
import { CATEGORY_ARTICLES_PER_PAGE } from "./CategoryArticles"
import { ArticlesGrid } from "../ArticlesGrid/ArticlesGrid"
@@ -15,6 +16,7 @@ export type CategoryArticlesInfiniteProps = {
export function RecentArticlesInfinite({ initialArticles, category }: CategoryArticlesInfiniteProps) {
const locale = useLocale()
+ const translations = useTranslations()
const {
data: categoryArticlesQuery,
@@ -41,7 +43,7 @@ export function RecentArticlesInfinite({ initialArticles, category }: CategoryAr
})
const articles = categoryArticlesQuery?.pages.flatMap((page) => page.articles)
- const buttonText = isFetchingNextPage ? "Loading" : "See more"
+ const buttonText = isFetchingNextPage ? translations.loading : translations.showMore
return (
<>
diff --git a/src/components/RecentArticles/RecentArticlesInfinite.tsx b/src/components/RecentArticles/RecentArticlesInfinite.tsx
index f8891091..c5317b60 100644
--- a/src/components/RecentArticles/RecentArticlesInfinite.tsx
+++ b/src/components/RecentArticles/RecentArticlesInfinite.tsx
@@ -4,6 +4,7 @@ import { useInfiniteQuery } from "@tanstack/react-query"
import { Button } from "@/components/ui/Button/Button"
import { GetRecentArticlesQuery } from "@/gql/graphql"
import { useLocale } from "@/i18n/i18n"
+import { useTranslations } from "@/i18n/useTranslations"
import { getRecentArticles } from "@/lib/client"
import { RECENT_ARTICLES_PER_PAGE } from "./RecentArticles"
import { ArticleCard, hygraphArticleToCardProps } from "../ArticleCard/ArticleCard"
@@ -14,6 +15,7 @@ export type RecentArticlesInfiniteProps = {
export function RecentArticlesInfinite({ initialArticles }: RecentArticlesInfiniteProps) {
const locale = useLocale()
+ const translations = useTranslations()
const {
data: recentArticlesQuery,
@@ -51,7 +53,7 @@ export function RecentArticlesInfinite({ initialArticles }: RecentArticlesInfini
{hasNextPage && (
)}
diff --git a/src/components/RecommendedArticles/RecommendedArticles.tsx b/src/components/RecommendedArticles/RecommendedArticles.tsx
index 551f86d6..23c749b7 100644
--- a/src/components/RecommendedArticles/RecommendedArticles.tsx
+++ b/src/components/RecommendedArticles/RecommendedArticles.tsx
@@ -2,6 +2,7 @@
import { useQuery } from "@tanstack/react-query"
import { useLocale } from "@/i18n/i18n"
+import { useTranslations } from "@/i18n/useTranslations"
import { getArticleRecommendedArticles } from "@/lib/client"
import { ArticleCard, hygraphArticleToCardProps } from "../ArticleCard/ArticleCard"
@@ -9,6 +10,7 @@ type RecommendedArticlesProps = { id: string }
export function RecommendedArticles({ id }: RecommendedArticlesProps) {
const locale = useLocale()
+ const translations = useTranslations()
const { data: recommendedArticles, isLoading } = useQuery({
queryKey: [`recommended-articles`, id],
queryFn: () => getArticleRecommendedArticles({ locale, id }),
@@ -17,7 +19,7 @@ export function RecommendedArticles({ id }: RecommendedArticlesProps) {
if (!isLoading && recommendedArticles?.length === 0) return null
return (
- Related articles
+ {translations.relatedArticles}
{isLoading &&
Array.from(Array(3).keys()).map((idx) => {
diff --git a/src/components/Search/RefinementCombobox.tsx b/src/components/Search/RefinementCombobox.tsx
index 493a0bba..0d1b2cfe 100644
--- a/src/components/Search/RefinementCombobox.tsx
+++ b/src/components/Search/RefinementCombobox.tsx
@@ -8,11 +8,13 @@ import { Button } from "@/components/ui/Button/Button"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/Command/Command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover/Popover"
+import { useTranslations } from "@/i18n/useTranslations"
import { cn } from "@/utils/cn"
type RefinementComboboxProps = UseRefinementListProps
export function RefinementCombobox(props: RefinementComboboxProps) {
+ const translations = useTranslations()
const { items, refine } = useRefinementList(props)
const [open, setOpen] = React.useState(false)
@@ -26,7 +28,7 @@ export function RefinementCombobox(props: RefinementComboboxProps) {
aria-expanded={open}
className="w-[150px] justify-between bg-gray-100 text-gray-400"
>
- {"Select tag..."}
+ {`${translations.selectTag}...`}
@@ -49,7 +51,7 @@ export function RefinementCombobox(props: RefinementComboboxProps) {
- No tags found.
+ {translations.noTagsFound}
{items
.sort((a, b) => a.label.localeCompare(b.label))
diff --git a/src/components/Search/SearchDialog.tsx b/src/components/Search/SearchDialog.tsx
index 6bec159e..242aa9b9 100644
--- a/src/components/Search/SearchDialog.tsx
+++ b/src/components/Search/SearchDialog.tsx
@@ -20,6 +20,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components
import { Input } from "@/components/ui/Input/Input"
import { env } from "@/env.mjs"
import { Locale, useLocale } from "@/i18n/i18n"
+import { useTranslations } from "@/i18n/useTranslations"
import { RefinementCombobox } from "./RefinementCombobox"
import { Tag } from "../ArticleCard/Buttons/Tag"
import { Popover } from "../ui/Popover/Popover"
@@ -125,11 +126,12 @@ function NoResultsBoundary({ children, fallback }: { children: ReactNode; fallba
function NoResults() {
const { indexUiState } = useInstantSearch()
+ const translations = useTranslations()
return (
- No results for {indexUiState.query}
.
+ {translations.noResultsFor} {indexUiState.query}
.
)
@@ -140,6 +142,7 @@ const queryHook: UseSearchBoxProps["queryHook"] = (query, search) => {
}
function DebouncedSearchBox() {
+ const translations = useTranslations()
const { refine, query } = useSearchBox({
queryHook,
})
@@ -153,7 +156,15 @@ function DebouncedSearchBox() {
debouncedRefine(value)
}
- return
+ return (
+
+ )
}
export default SearchDialogContent
diff --git a/src/components/ShareOnSocial/ShareOnSocial.tsx b/src/components/ShareOnSocial/ShareOnSocial.tsx
index ac736596..84e91e37 100644
--- a/src/components/ShareOnSocial/ShareOnSocial.tsx
+++ b/src/components/ShareOnSocial/ShareOnSocial.tsx
@@ -1,5 +1,6 @@
import { Facebook, Linkedin, Twitter } from "lucide-react"
import { useLocale } from "@/i18n/i18n"
+import { getTranslations } from "@/i18n/setTranslations"
type ShareOnSocialProps = {
articleTitle: string
@@ -7,6 +8,8 @@ type ShareOnSocialProps = {
}
export function ShareOnSocial({ articleTitle, articleUrl }: ShareOnSocialProps) {
+ const translations = getTranslations()
+
const locale = useLocale()
const encodedTitle = encodeURIComponent(articleTitle)
const encodedUrl = encodeURIComponent(articleUrl)
@@ -17,7 +20,7 @@ export function ShareOnSocial({ articleTitle, articleUrl }: ShareOnSocialProps)
return (
-
Share on social:
+
{translations.shareOnSocial}:
diff --git a/src/i18n/setTranslations.ts b/src/i18n/setTranslations.ts
new file mode 100644
index 00000000..4dcb52c9
--- /dev/null
+++ b/src/i18n/setTranslations.ts
@@ -0,0 +1,46 @@
+import pickBy from "lodash/pickBy"
+import { cache } from "react"
+import { GetGlobalTranslationsQuery } from "@/gql/graphql"
+import { getGlobalTranslations } from "@/lib/client"
+import { Locale } from "./i18n"
+
+type NonNullableProperty = { [P in keyof T]: NonNullable }
+type RequiredNonNullable = Required>
+export type GlobalTranslations = RequiredNonNullable<
+ Omit["translations"]>, "__typename">
+>
+
+export const defaultTranslations = {
+ showMore: "Show More (fallback)",
+ showing: "Showing",
+ resultsFor: "results for",
+ searchCategory: "Search Category",
+ search: "Search (fallback)",
+ selectTag: "Select tag (fallback)",
+ shareOnSocial: "Share on social",
+ relatedArticles: "Related articles",
+ noTagsFound: "No tags found.",
+ noResultsFor: "No results for",
+ loading: "Loading",
+}
+
+const getCache = cache(() => {
+ const value: { translations: GlobalTranslations } = {
+ translations: defaultTranslations,
+ }
+ return value
+})
+
+export async function setTranslations(locale: Locale) {
+ const translations = await getGlobalTranslations({ locale })
+
+ const merged = translations
+ ? { ...defaultTranslations, ...pickBy(translations, (val) => Boolean(val)) }
+ : defaultTranslations
+ getCache().translations = merged
+ return merged
+}
+
+export function getTranslations() {
+ return getCache().translations
+}
diff --git a/src/i18n/useTranslations.tsx b/src/i18n/useTranslations.tsx
new file mode 100644
index 00000000..45351579
--- /dev/null
+++ b/src/i18n/useTranslations.tsx
@@ -0,0 +1,24 @@
+"use client"
+import { createContext, ReactNode, useContext } from "react"
+import { GlobalTranslations } from "./setTranslations"
+
+const TranslationsContext = createContext(null)
+
+export const TranslationsProvider = ({
+ children,
+ translations,
+}: {
+ children: ReactNode
+ translations: GlobalTranslations
+}) => {
+ return {children}
+}
+
+export const useTranslations = () => {
+ const context = useContext(TranslationsContext)
+ if (!context) {
+ throw new Error("useTranslations must be used within a TranslationsProvider")
+ }
+
+ return context
+}
diff --git a/src/lib/client.ts b/src/lib/client.ts
index 6266128b..75c5afb6 100644
--- a/src/lib/client.ts
+++ b/src/lib/client.ts
@@ -19,6 +19,7 @@ import { getHomepageMetadataQuery, getHomepageQuery, getNavigationQuery } from "
import { getPageBySlugQuery, getPageMetadataBySlugQuery, listPagesForSitemapQuery } from "./queries/pages"
import { getQuizQuestionsByIdQuery } from "./queries/quizes"
import { Tag } from "./tags"
+import { getGlobalTranslationsQuery } from "./queries/translations"
const throttle = pThrottle({
limit: 5, //Community: 5req/sec
@@ -256,3 +257,13 @@ export async function getQuizQuestionsById(variables: { locale: Locale; id: stri
})
return quiz?.question ?? null
}
+
+export async function getGlobalTranslations(variables: { locale: Locale }) {
+ const { translationsSingleton } = await graphqlFetch({
+ cache: "force-cache",
+ document: getGlobalTranslationsQuery,
+ tags: ["TRANSLATIONS"],
+ variables,
+ })
+ return translationsSingleton?.translations
+}
diff --git a/src/lib/queries/translations.ts b/src/lib/queries/translations.ts
new file mode 100644
index 00000000..480c4083
--- /dev/null
+++ b/src/lib/queries/translations.ts
@@ -0,0 +1,23 @@
+import { graphql } from "@/gql"
+
+export const getGlobalTranslationsQuery = graphql(`
+ query getGlobalTranslations($locales: [Locale!]!) {
+ translationsSingleton: singleton(where: { key: "translations" }, locales: $locales) {
+ translations: model {
+ ... on GlobalTranslations {
+ showMore
+ showing
+ resultsFor
+ searchCategory
+ search
+ selectTag
+ shareOnSocial
+ relatedArticles
+ noTagsFound
+ noResultsFor
+ loading
+ }
+ }
+ }
+ }
+`)
diff --git a/src/lib/tags.ts b/src/lib/tags.ts
index a89b18f1..10e875d6 100644
--- a/src/lib/tags.ts
+++ b/src/lib/tags.ts
@@ -1 +1 @@
-export type Tag = "PAGE" | "ARTICLE" | "CATEGORY" | "NAVIGATION" | "FOOTER" | "HOMEPAGE" | "QUIZ"
+export type Tag = "PAGE" | "ARTICLE" | "CATEGORY" | "NAVIGATION" | "FOOTER" | "HOMEPAGE" | "QUIZ" | "TRANSLATIONS"