Skip to content

Commit

Permalink
Merge pull request #94 from Blazity/g-translations
Browse files Browse the repository at this point in the history
feat: add global translations
  • Loading branch information
Pierniki committed Nov 9, 2023
2 parents 2efb2be + ec147a5 commit 7d2c955
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 18 deletions.
14 changes: 12 additions & 2 deletions src/app/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ReactNode, useState } from "react"
import { TooltipProvider } from "@/components/ui/Tooltip/Tooltip"
import { GlobalTranslations } from "@/i18n/setTranslations"
import { TranslationsProvider } from "@/i18n/useTranslations"

export default function Providers({ children }: { children: ReactNode }) {
export default function Providers({
children,
translations,
}: {
children: ReactNode
translations: GlobalTranslations
}) {
const [queryClient] = useState(
() =>
new QueryClient({
Expand All @@ -18,7 +26,9 @@ export default function Providers({ children }: { children: ReactNode }) {
)
return (
<QueryClientProvider client={queryClient}>
<TooltipProvider>{children}</TooltipProvider>
<TranslationsProvider translations={translations}>
<TooltipProvider>{children}</TooltipProvider>
</TranslationsProvider>
</QueryClientProvider>
)
}
7 changes: 6 additions & 1 deletion src/app/[lang]/category/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Metadata } from "next/types"
import { unstable_setRequestLocale } from "next-intl/server"
import { CategoryArticles } from "@/components/CategoryArticles/CategoryArticles"
import { Locale } from "@/i18n/i18n"
import { setTranslations } from "@/i18n/setTranslations"
import { getMatadataObj } from "@/utils/getMetadataObj"

type ArticlePageProps = { params: { slug: string; lang: Locale } }
Expand All @@ -9,6 +11,9 @@ export async function generateMetadata({ params: { slug } }: ArticlePageProps):
return getMatadataObj({ title: `Category - ${slug}` })
}

export default async function Web({ params: { slug } }: ArticlePageProps) {
export default async function Web({ params: { slug, lang } }: ArticlePageProps) {
unstable_setRequestLocale(lang)
await setTranslations(lang)

return <CategoryArticles category={slug} />
}
5 changes: 3 additions & 2 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Navigation } from "@/components/Navigation/Navigation"
import { env } from "@/env.mjs"
import { i18n, type Locale } from "@/i18n/i18n"
import "@/styles/tailwind.css"
import { setTranslations } from "@/i18n/setTranslations"
import { getNavigation } from "@/lib/client"
import { GoogleAnalytics } from "../GoogleAnalytics"
import Providers from "../Providers"
Expand Down Expand Up @@ -40,13 +41,13 @@ export default async function Layout({ children, params }: { children: React.Rea
const isValidLocale = i18n.locales.some((cur) => cur === locale)
if (!isValidLocale) notFound()
unstable_setRequestLocale(locale)

const translations = await setTranslations(locale)
const { navigation, footer, logo } = await getNavigation(locale)

return (
<html lang={locale}>
<GoogleAnalytics />
<Providers>
<Providers translations={translations}>
<body className="flex min-h-screen flex-col items-center ">
<div className="z-50 flex w-full justify-center border-b bg-white">
<nav className="flex w-full max-w-[1200px] items-center justify-end gap-4 py-4">
Expand Down
2 changes: 2 additions & 0 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RecentArticles } from "@/components/RecentArticles/RecentArticles"
import { StockDisplay } from "@/components/StockDisplay/StockDisplay"
import { TrendingArticles } from "@/components/TrendingArticles/TrendingArticles"
import { i18n, Locale } from "@/i18n/i18n"
import { setTranslations } from "@/i18n/setTranslations"
import { getHomepage, getHomepageMetadata } from "@/lib/client"
import { getMatadataObj } from "@/utils/getMetadataObj"

Expand All @@ -25,6 +26,7 @@ export async function generateMetadata({ params }: { params: { lang: Locale } })
export default async function Web({ params }: { params: { lang: Locale } }) {
unstable_setRequestLocale(params.lang)
const homepage = await getHomepage(params.lang)
await setTranslations(params.lang)

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ArticleCard/ArticleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ export function ArticleCard({
quality={100}
sizes="(max-width: 220px) 82px, 480px, (max-width: 640px) 480px, 780px, (max-width: 1024px) 780px, 1020px"
className={cn(
"h-[82px] min-h-[82px] w-full rounded-xl object-cover text-center brightness-90 md:h-[264px] md:min-h-[264px] md:rounded-none",
"h-[82px] min-h-[82px] w-full rounded-xl bg-gradient-to-br from-gray-200 to-gray-300 object-cover text-center brightness-90 md:h-[264px] md:min-h-[264px] md:rounded-none",
isMain && "h-[264px] min-h-[264px] rounded-none"
)}
/>
)}
<div
className={cn(
"absolute inset-0 z-20 hidden w-full flex-col items-start justify-end p-6 md:flex",
"absolute inset-0 z-20 hidden w-full flex-col items-start justify-end p-6 md:flex",
isMain && "flex"
)}
>
Expand Down
6 changes: 4 additions & 2 deletions src/components/CategoryArticles/CategoryArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -22,8 +24,8 @@ export async function CategoryArticles({ category }: CategoryArticlesProps) {
return (
<section className="w-full">
<div className="mb-10 w-full border-b-[1px] py-14">
<h2 className="mb-6 text-3xl font-bold">Search Category</h2>
<p className="mb-2 text-xs">Showing {articles.count} results for: </p>
<h2 className="mb-6 text-3xl font-bold">{translations.searchCategory}</h2>
<p className="mb-2 text-xs">{`${translations.showing} ${articles.count} ${translations.resultsFor}`}</p>
<p className="text-xl font-bold">&quot;{category}&quot;</p>
</div>
<div className="mx-auto w-full">
Expand Down
4 changes: 3 additions & 1 deletion src/components/CategoryArticles/CategoryArticlesInfinite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -15,6 +16,7 @@ export type CategoryArticlesInfiniteProps = {

export function RecentArticlesInfinite({ initialArticles, category }: CategoryArticlesInfiniteProps) {
const locale = useLocale()
const translations = useTranslations()

const {
data: categoryArticlesQuery,
Expand All @@ -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 (
<>
Expand Down
4 changes: 3 additions & 1 deletion src/components/RecentArticles/RecentArticlesInfinite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -14,6 +15,7 @@ export type RecentArticlesInfiniteProps = {

export function RecentArticlesInfinite({ initialArticles }: RecentArticlesInfiniteProps) {
const locale = useLocale()
const translations = useTranslations()

const {
data: recentArticlesQuery,
Expand Down Expand Up @@ -51,7 +53,7 @@ export function RecentArticlesInfinite({ initialArticles }: RecentArticlesInfini
</div>
{hasNextPage && (
<Button className="w-full rounded-xl border p-4" disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>
See more
{translations.showMore}
</Button>
)}
</section>
Expand Down
4 changes: 3 additions & 1 deletion src/components/RecommendedArticles/RecommendedArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

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"

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 }),
Expand All @@ -17,7 +19,7 @@ export function RecommendedArticles({ id }: RecommendedArticlesProps) {
if (!isLoading && recommendedArticles?.length === 0) return null
return (
<section className="w-full py-4">
<h2 className="mb-8 text-2xl font-bold">Related articles</h2>
<h2 className="mb-8 text-2xl font-bold">{translations.relatedArticles}</h2>
<div className={`grid gap-8 md:grid-cols-3`}>
{isLoading &&
Array.from(Array(3).keys()).map((idx) => {
Expand Down
6 changes: 4 additions & 2 deletions src/components/Search/RefinementCombobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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}...`}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
Expand All @@ -49,7 +51,7 @@ export function RefinementCombobox(props: RefinementComboboxProps) {
<PopoverContent className="ml-6 w-[200px] bg-white p-0">
<Command>
<CommandInput placeholder="Search tags.." />
<CommandEmpty>No tags found.</CommandEmpty>
<CommandEmpty>{translations.noTagsFound}</CommandEmpty>
<CommandGroup value={"value"}>
{items
.sort((a, b) => a.label.localeCompare(b.label))
Expand Down
15 changes: 13 additions & 2 deletions src/components/Search/SearchDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -125,11 +126,12 @@ function NoResultsBoundary({ children, fallback }: { children: ReactNode; fallba

function NoResults() {
const { indexUiState } = useInstantSearch()
const translations = useTranslations()

return (
<div className="flex w-full justify-center px-2 py-4">
<p className=" text-sm text-slate-600">
No results for <q>{indexUiState.query}</q>.
{translations.noResultsFor} <q>{indexUiState.query}</q>.
</p>
</div>
)
Expand All @@ -140,6 +142,7 @@ const queryHook: UseSearchBoxProps["queryHook"] = (query, search) => {
}

function DebouncedSearchBox() {
const translations = useTranslations()
const { refine, query } = useSearchBox({
queryHook,
})
Expand All @@ -153,7 +156,15 @@ function DebouncedSearchBox() {
debouncedRefine(value)
}

return <Input type="search" value={inputValue} onChange={onChange} placeholder="Search..." aria-label="Search" />
return (
<Input
type="search"
value={inputValue}
onChange={onChange}
placeholder={`${translations.search}...`}
aria-label={translations.search}
/>
)
}

export default SearchDialogContent
5 changes: 4 additions & 1 deletion src/components/ShareOnSocial/ShareOnSocial.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Facebook, Linkedin, Twitter } from "lucide-react"
import { useLocale } from "@/i18n/i18n"
import { getTranslations } from "@/i18n/setTranslations"

type ShareOnSocialProps = {
articleTitle: string
articleUrl: string
}

export function ShareOnSocial({ articleTitle, articleUrl }: ShareOnSocialProps) {
const translations = getTranslations()

const locale = useLocale()
const encodedTitle = encodeURIComponent(articleTitle)
const encodedUrl = encodeURIComponent(articleUrl)
Expand All @@ -17,7 +20,7 @@ export function ShareOnSocial({ articleTitle, articleUrl }: ShareOnSocialProps)

return (
<div className="flex items-center justify-between gap-2 py-5 lg:justify-normal">
<p className="pr-3 text-sm opacity-60">Share on social:</p>
<p className="pr-3 text-sm opacity-60">{translations.shareOnSocial}:</p>
<div className="flex items-center gap-2">
<a href={twitterShareUrl} aria-label="Twitter" hrefLang={locale} className="rounded-xl bg-black p-2">
<Twitter fill="white" stroke="none" />
Expand Down
46 changes: 46 additions & 0 deletions src/i18n/setTranslations.ts
Original file line number Diff line number Diff line change
@@ -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<T> = { [P in keyof T]: NonNullable<T[P]> }
type RequiredNonNullable<T> = Required<NonNullableProperty<T>>
export type GlobalTranslations = RequiredNonNullable<
Omit<NonNullable<NonNullable<GetGlobalTranslationsQuery["translationsSingleton"]>["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
}
24 changes: 24 additions & 0 deletions src/i18n/useTranslations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client"
import { createContext, ReactNode, useContext } from "react"
import { GlobalTranslations } from "./setTranslations"

const TranslationsContext = createContext<GlobalTranslations | null>(null)

export const TranslationsProvider = ({
children,
translations,
}: {
children: ReactNode
translations: GlobalTranslations
}) => {
return <TranslationsContext.Provider value={translations}>{children}</TranslationsContext.Provider>
}

export const useTranslations = () => {
const context = useContext(TranslationsContext)
if (!context) {
throw new Error("useTranslations must be used within a TranslationsProvider")
}

return context
}
Loading

0 comments on commit 7d2c955

Please sign in to comment.