add more translation

This commit is contained in:
Kalidou Diagne
2024-01-16 15:05:00 +00:00
parent 9e49507e27
commit 738cb6a980
11 changed files with 178 additions and 137 deletions

View File

@@ -137,7 +137,7 @@ export default async function ProjectDetailPage({ params }: PageProps) {
<div className="flex w-full flex-col gap-5 text-base font-normal leading-relaxed">
<Markdown>{currProject.description}</Markdown>
</div>
<ProjectExtraLinks project={currProject} />
<ProjectExtraLinks project={currProject} lang={lang} />
</div>
</div>
</div>

View File

@@ -15,7 +15,7 @@ import { LocaleTypes, cookieName, getOptions, languages } from "./settings"
const runsOnServerSide = typeof window === "undefined"
//
i18next
export const i18n = i18next
.use(initReactI18next)
.use(LanguageDetector)
.use(

View File

@@ -1,4 +1,4 @@
import { createInstance } from "i18next"
import { createInstance, init } from "i18next"
import resourcesToBackend from "i18next-resources-to-backend"
import { initReactI18next } from "react-i18next/initReactI18next"

View File

@@ -30,6 +30,21 @@
"description": "The page you are looking for might have been removed, had its name changed or is temporarily unavailable."
}
},
"tags": {
"build": "Build",
"play": "Play",
"research": "Research"
},
"status": {
"archived": "Archived",
"active": "Active"
},
"sortBy": "Sort by: {{option}}",
"tryItOut": "Try it out!",
"learnMore": "Learn more",
"buildWithThisTool": "Build with this tool",
"deepDiveResearch": "Dive deeper into the research",
"searchProjectPlaceholder": "Search project title or keyword",
"close": "Close",
"lastUpdatedAt": "Last updated {{date}}",
"projectLibrary": "Project Library",

View File

@@ -58,7 +58,7 @@ export default function ProjectCard({
return (
<div
onClick={() => router.push(`${lang}/projects/${id}`)}
onClick={() => router.push(`/projects/${id}`)}
className={cn(projectCardVariants({ showLinks, border, className }))}
>
{showBanner && (
@@ -67,14 +67,14 @@ export default function ProjectCard({
alt={`${name} banner`}
width={1200}
height={630}
className="w-full rounded-t-lg object-cover"
className="w-full rounded-t-lg object-cover min-h-[160px]"
/>
)}
<div className="flex h-full flex-col justify-between gap-5 rounded-b-lg bg-white p-5">
<div className="flex flex-col justify-start gap-2">
<div className="mb-2 flex gap-2">
{tags?.themes?.map((theme, index) => {
const { label } = ThemesButtonMapping?.[theme]
const { label } = ThemesButtonMapping(lang)?.[theme]
const icon = TagsIconMapping?.[theme]
return (

View File

@@ -33,7 +33,8 @@ type IProjectTags = {
}
export function ProjectTags({ project, lang }: IProjectTags) {
const { label, icon } = ThemesStatusMapping?.[project?.projectStatus] ?? {}
const statusItem = ThemesStatusMapping(lang)
const { label, icon } = statusItem?.[project?.projectStatus] ?? {}
const { t } = useTranslation(lang, "common")
return (

View File

@@ -1,3 +1,5 @@
"use client"
import React from "react"
import Link from "next/link"
@@ -6,35 +8,14 @@ import {
ProjectExtraLinkType,
ProjectInterface,
} from "@/lib/types"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { Icons } from "../icons"
const ExtraLinkLabelMapping: Record<
ProjectExtraLinkType,
{
label: string
icon?: any
}
> = {
buildWith: {
label: "Build with this tool",
icon: <Icons.hammer />,
},
play: {
label: "Try it out!",
icon: <Icons.hand />,
},
research: {
label: "Dive deeper into the research",
icon: <Icons.readme />,
},
learn: {
label: "Learn more",
},
}
interface ProjectExtraLinksProps {
project: ProjectInterface
lang: LocaleTypes
}
interface ExtraLinkItemsProps {
@@ -42,43 +23,71 @@ interface ExtraLinkItemsProps {
links: ActionLinkTypeLink[]
}
const ExtraLinkItems = ({ id, links = [] }: ExtraLinkItemsProps) => {
const { label, icon } = ExtraLinkLabelMapping[id]
if (!links.length) return null // no links hide the section
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
{icon && <span className="text-anakiwa-500">{icon}</span>}
<p className="font-sans text-xl font-medium text-tuatara-700">
{label}
</p>
</div>
<div className="flex flex-col items-start gap-2">
{links.map(({ label, url }: ActionLinkTypeLink) => {
return (
<Link
href={url}
target="_blank"
className="flex cursor-pointer items-center gap-1 overflow-hidden border-b-2 border-transparent font-sans font-normal text-tuatara-950 duration-200 ease-in-out hover:border-orange"
>
{label}
<Icons.externalUrl />
</Link>
)
})}
</div>
</div>
)
}
export default function ProjectExtraLinks({ project }: ProjectExtraLinksProps) {
export default function ProjectExtraLinks({
project,
lang,
}: ProjectExtraLinksProps) {
const { t } = useTranslation(lang, "common")
const { extraLinks = {} } = project
const hasExtraLinks = Object.keys(extraLinks).length > 0
const ExtraLinkLabelMapping: Record<
ProjectExtraLinkType,
{
label: string
icon?: any
}
> = {
buildWith: {
label: t("buildWithThisTool"),
icon: <Icons.hammer />,
},
play: {
label: t("tryItOut"),
icon: <Icons.hand />,
},
research: {
label: t("deepDiveResearch"),
icon: <Icons.readme />,
},
learn: {
label: t("learnMore"),
},
}
if (!hasExtraLinks) return null
const ExtraLinkItems = ({ id, links = [] }: ExtraLinkItemsProps) => {
const { label, icon } = ExtraLinkLabelMapping[id]
if (!links.length) return null // no links hide the section
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
{icon && <span className="text-anakiwa-500">{icon}</span>}
<p className="font-sans text-xl font-medium text-tuatara-700">
{label}
</p>
</div>
<div className="flex flex-col items-start gap-2">
{links.map(({ label, url }: ActionLinkTypeLink) => {
return (
<Link
href={url}
target="_blank"
className="flex cursor-pointer items-center gap-1 overflow-hidden border-b-2 border-transparent font-sans font-normal text-tuatara-950 duration-200 ease-in-out hover:border-orange"
>
{label}
<Icons.externalUrl />
</Link>
)
})}
</div>
</div>
)
}
return (
<div className="flex flex-col gap-8 py-4">
{Object.entries(ExtraLinkLabelMapping).map(([key]) => {

View File

@@ -11,10 +11,10 @@ import {
ProjectFilter,
useProjectFiltersState,
} from "@/state/useProjectFiltersState"
import i18next from "i18next"
import { useDebounce } from "react-use"
import { LangProps } from "@/types/common"
import { ProjectStatusType } from "@/lib/types"
import { IThemeStatus, IThemesButton, LangProps } from "@/types/common"
import { cn, queryStringToObject } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
@@ -41,49 +41,45 @@ const FilterWrapper = ({ label, children }: FilterWrapperProps) => {
)
}
export const ThemesButtonMapping: Record<
string,
{
label: string
icon: any
export const ThemesButtonMapping = (lang: LocaleTypes): IThemesButton => {
const t = i18next.getFixedT(lang, "common")
return {
build: {
label: t("tags.build"),
icon: <Icons.hammer />,
},
play: {
label: t("tags.play"),
icon: <Icons.hand />,
},
research: {
label: t("tags.research"),
icon: <Icons.readme />,
},
}
> = {
build: {
label: "Build",
icon: <Icons.hammer />,
},
play: {
label: "Play",
icon: <Icons.hand />,
},
research: {
label: "Research",
icon: <Icons.readme />,
},
}
export const ThemesStatusMapping: Partial<
Record<
ProjectStatusType,
{
label: string
icon: any
}
>
> = {
active: {
label: "Active",
icon: <Icons.checkActive />,
},
archived: {
label: "Archived",
icon: <Icons.archived />,
},
export const ThemesStatusMapping = (lang: LocaleTypes): IThemeStatus => {
const t = i18next.getFixedT(lang, "common")
return {
active: {
label: t("status.active"),
icon: <Icons.checkActive />,
},
archived: {
label: t("status.archived"),
icon: <Icons.archived />,
},
}
}
const FilterButtons = ({
searchQuery,
lang,
}: {
lang: LocaleTypes
searchQuery?: string
}): JSX.Element => {
const { activeFilters, onSelectTheme } = useProjectFiltersState(
@@ -92,25 +88,27 @@ const FilterButtons = ({
return (
<div className="relative col-span-1 grid grid-cols-3 gap-2 after:absolute after:right-[-25px] after:h-11 after:w-[1px] after:content-none md:col-span-2 md:gap-4 md:after:content-['']">
{Object.entries(ThemesButtonMapping).map(([key, { label, icon }]) => {
const isActive = activeFilters?.themes?.includes(key)
const variant = isActive ? "blue" : "white"
return (
<Button
key={key}
variant={variant}
size="lg"
onClick={() => {
onSelectTheme(key, searchQuery ?? "")
}}
>
<div className="flex items-center gap-2">
{icon}
<span>{label}</span>
</div>
</Button>
)
})}
{Object.entries(ThemesButtonMapping(lang)).map(
([key, { label, icon }]) => {
const isActive = activeFilters?.themes?.includes(key)
const variant = isActive ? "blue" : "white"
return (
<Button
key={key}
variant={variant}
size="lg"
onClick={() => {
onSelectTheme(key, searchQuery ?? "")
}}
>
<div className="flex items-center gap-2">
{icon}
<span>{label}</span>
</div>
</Button>
)
}
)}
</div>
)
}
@@ -128,7 +126,7 @@ export default function ProjectFiltersBar({ lang }: LangProps["params"]) {
useEffect(() => {
if (!queryString) return
router.push(`${lang}/projects?${queryString}`)
router.push(`/projects?${queryString}`)
}, [queryString, router, lang])
useEffect(() => {
@@ -152,7 +150,7 @@ export default function ProjectFiltersBar({ lang }: LangProps["params"]) {
projects,
})
setSearchQuery("") // clear input
router.push(`${lang}/projects`)
router.push(`/projects`)
}
useDebounce(
@@ -235,7 +233,7 @@ export default function ProjectFiltersBar({ lang }: LangProps["params"]) {
}
if (type === "button") {
const { icon, label } = ThemesButtonMapping[item]
const { icon, label } = ThemesButtonMapping(lang)[item]
if (!isActive) return null
return (
<div>
@@ -272,14 +270,14 @@ export default function ProjectFiltersBar({ lang }: LangProps["params"]) {
<div className="flex flex-col gap-6">
<span className="text-lg font-medium">{t("whatDoYouWantDoToday")}</span>
<div className="grid grid-cols-1 items-center justify-between gap-3 md:grid-cols-5 md:gap-12">
<FilterButtons />
<FilterButtons lang={lang} />
<div className="col-span-1 grid grid-cols-[1fr_auto] gap-2 md:col-span-3 md:gap-3">
<Input
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setSearchQuery(e?.target?.value)
}
value={searchQuery}
placeholder="Search project title or keyword"
placeholder={t("searchProjectPlaceholder")}
/>
<div className="flex items-center gap-3">
<Badge value={filterCount}>

View File

@@ -14,17 +14,6 @@ import { Dropdown } from "../ui/dropdown"
const labelClass = "h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-lg"
const projectSortItems: { label: string; value: ProjectSortBy }[] = [
{ label: "Random", value: "random" },
{ label: "Title: A-Z", value: "asc" },
{ label: "Title: Z-A", value: "desc" },
{ label: "Relevance", value: "relevance" },
]
const getSortLabel = (sortBy: ProjectSortBy) => {
return projectSortItems.find((item) => item.value === sortBy)?.label || sortBy
}
export const ProjectResultBar = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "common")
const { activeFilters, toggleFilter, projects, sortProjectBy, sortBy } =
@@ -41,12 +30,23 @@ export const ProjectResultBar = ({ lang }: LangProps["params"]) => {
}
)
const projectSortItems: { label: string; value: ProjectSortBy }[] = [
{ label: t("filterOptions.random"), value: "random" },
{ label: t("filterOptions.asc"), value: "asc" },
{ label: t("filterOptions.desc"), value: "desc" },
{ label: t("filterOptions.relevance"), value: "relevance" },
]
const activeSortOption = t("sortBy", {
option: t(`filterOptions.${sortBy}`),
})
return (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className={labelClass}>{resultLabel}</span>
<Dropdown
label={`Sort by: ${getSortLabel(sortBy)}`}
label={activeSortOption}
defaultItem="random"
items={projectSortItems}
onChange={(sortBy) => sortProjectBy(sortBy as ProjectSortBy)}

View File

@@ -1,7 +1,6 @@
"use client"
import { LangProps } from "@/types/common"
import { siteConfig } from "@/config/site"
import { MainNav, MainNavProps } from "@/components/main-nav"
import { useTranslation } from "@/app/i18n/client"

View File

@@ -1,3 +1,4 @@
import { ProjectStatusType } from "@/lib/types"
import { LocaleTypes } from "@/app/i18n/settings"
export interface LangProps {
@@ -5,3 +6,21 @@ export interface LangProps {
lang: LocaleTypes
}
}
export type IThemeStatus = Partial<
Record<
ProjectStatusType,
{
label: string
icon: any
}
>
>
export type IThemesButton = Record<
string,
{
label: string
icon: any
}
>