Skip to content

Commit

Permalink
I18n locales translations (#85)
Browse files Browse the repository at this point in the history
* feat: landing page setup for multiple language switching

* feat: setup i18n config and translations for spanish

* feat: landing page translations

* feat: use translations for explore page

* feat: about page language routes and translations

* feat: add zh, pt translations, complete es translation

* fix: cleanup back links

* feat: translations on search page

* feat: translations on transcript page

* feat: mobile nav transalations

* fix: absolute route on result card

* fix: cleanups

* feat: website available-in languages

* fix: language links for types and categories card on homepage

* fix: og-image for sources account for languages and nesting

* fix: restrict back cta width, cleanups
  • Loading branch information
Emmanuel-Develops committed Feb 1, 2025
1 parent 21c80ab commit 9c2095d
Show file tree
Hide file tree
Showing 65 changed files with 1,838 additions and 644 deletions.
19 changes: 5 additions & 14 deletions contentlayer.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
Source as ContentSourceType,
} from "./.contentlayer/generated/types";
import { LanguageCode, OtherSupportedLanguages } from "./src/config";
import { getTopicTitle } from "./src/utils/topic";

const Resources = defineNestedType(() => ({
name: "Resources",
Expand Down Expand Up @@ -437,21 +438,11 @@ export const Transcript = defineDocumentType(() => ({
const topicsStore = doc?.tags as any || [];
const topics = (topicsStore?._array as string[]) ?? [];

const topicIndex = getTopics() as Topic[];

const topicsWithTitles = topics.map((topic) => {
const currentTopic = getTopics().find(
(topicData: FieldCountItem) => topicData.slug === topic
);

if(currentTopic?.title && currentTopic?.title.includes("(Miscellaneous)")) {
return {
name: currentTopic?.title.replace("(Miscellaneous)",""),
slug: currentTopic.slug,
}
}
return {
name: currentTopic?.title || topic,
slug: currentTopic?.slug || topic,
};
const topicTitle = getTopicTitle(topic, topicIndex);
return { name: topicTitle, slug: topic };
});
return topicsWithTitles;
},
Expand Down
32 changes: 0 additions & 32 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,10 @@ const nextConfig = {
rewrites: async () => {
return {
fallback: [
// {
// source: "/:path*.:ext([a-zA-Z0-9_+]{1,4})", // Match extensions that are 1-4 AlphaNumeric characters long
// destination: "/gh-pages/:path*.:ext", // Rewrite to gh-pages/[path_here].ext
// },
// {
// source: "/tags/:path",
// destination: "/gh-pages/tags/:path/index.html",
// },
// {
// source: "/speakers/:path",
// destination: "/gh-pages/speakers/:path/index.html",
// },
// {
// source: "/es",
// destination: "/gh-pages/es/index.html",
// },
// {
// source: "/zh",
// destination: "/gh-pages/zh/index.html",
// },
// {
// source: "/pt",
// destination: "/gh-pages/pt/index.html",
// },
// {
// source: "/topics",
// destination: "/en/topics"
// },
{
source: "/:path((?!.*\\.[a-zA-Z0-9]{1,4}$).*)", // Matches paths without a valid file extension
destination: "/transcript/:path*", // Rewrite to /transcripts/[path...]
},
// {
// source: "/:path*",
// destination: "/gh-pages/:path*/index.html",
// },
],
};
},
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"lint": "next lint"
},
"dependencies": {
"@bitcoin-dev-project/bdp-ui": "^1.5.2",
"@bitcoin-dev-project/bdp-ui": "^1.5.3",
"@elastic/elasticsearch": "^8.16.2",
"@tanstack/react-query": "^5.62.0",
"@uiw/react-markdown-preview": "^5.1.3",
Expand Down
Binary file modified public/images/hero-desktop-image.webp
Binary file not shown.
97 changes: 43 additions & 54 deletions src/app/(explore)/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { ContentTreeArray, previewImageDimensions } from "@/utils/data";
import LinkIcon from "/public/svgs/link-icon.svg";
import WorldIcon from "/public/svgs/world-icon.svg";
import allSources from "@/public/sources-data.json";
import allSourcesCount from "@/public/source-count-data.json";
import {
constructSlugPaths,
convertSlugToLanguageSlug,
deriveAlternateLanguages,
deriveSourcesList,
fetchTranscriptDetails,
loopArrOrObject,
unsluggify,
} from "@/utils";
import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons";
import {
Expand All @@ -28,6 +27,8 @@ import { LanguageCode, LanguageCodes, OtherSupportedLanguages } from "@/config";
import { SourceTree } from "@/types";
import { arrayWithoutElementAtIndex, traverseSourceTree } from "@/utils/sources";
import { parseLanguageString } from "@/utils/locale";
import useTranslations from "@/hooks/useTranslations";
import { buildMetadata } from "@/utils/metadata";

// forces 404 for paths not generated from `generateStaticParams` function.
export const dynamicParams = false;
Expand All @@ -54,18 +55,22 @@ export function generateStaticParams() {
return combineSlugs;
}

const checkIfSourcesLanguageRoute = (slug: string[]) => slug.length === 2 && OtherSupportedLanguages.includes(slug[0] as LanguageCode) && slug[1] === "sources";
export const generateMetadata = async ({params}: { params: { slug: string[] } }): Promise<Metadata> => {

const slug = params.slug ?? [];

const slugUrl = slug.join("/");

export const generateMetadata = async ({params}: { params: { slug: string[] } }): Promise<Metadata> => {
// convert the slug to language slug, ensuring that the language code is included as the first element of the array
const languagePath = convertSlugToLanguageSlug(slug);

const isSourcesLanguageRoute = checkIfSourcesLanguageRoute(params.slug);
const language = languagePath[0];

const id = params.slug[0]
const foundSource = allSourcesCount.find((source) => source.slug === id);
// is it base source path, e.g [ 'sources' ] (which is converted to [ 'en', 'sources' ]) or [ '{language}', 'sources' ] (which is not converted)
const isSourcesLanguageRoute = languagePath.length === 2 && languagePath[1] === "sources";

if (isSourcesLanguageRoute) {
const languageCode = params.slug[0] as LanguageCode;
const languageCode = languagePath[0] as LanguageCode;
const { alternateLanguages, metadataLanguages } = deriveAlternateLanguages({
languageCode,
languages: LanguageCodes,
Expand All @@ -78,33 +83,16 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
canonical: "/sources",
languages: metadataLanguages // Add custom metadata for languages
},
openGraph:{
images:[{
url:`/api/og-image/${foundSource?.slug}`,
width: previewImageDimensions.width,
height: previewImageDimensions.height,
alt: `${foundSource?.name} OG Image`
}]
},
twitter:{
images:[{
url:`/api/og-image/${foundSource?.slug}`,
width: previewImageDimensions.width,
height: previewImageDimensions.height,
alt: `${foundSource?.name} OG Image`
}]
},
other: {
alternateLanguages,
language: languageCode
}
};
}


const slug = params.slug ?? [];
const contentTree = allSources as SourceTree;
const { slugPaths } = constructSlugPaths(slug);

let current: any = contentTree;

for (const part of slugPaths) {
Expand All @@ -115,8 +103,7 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
}
}

const language = slugPaths[1];

// for e.g [ 'advancing-bitcoin', 'en' ], [ 'advancing-bitcoin', 'es' ] etc
if (slugPaths.length === 2) {
// returns all language keys for the current source
const languages = Object.keys(contentTree[slugPaths[0]]);
Expand All @@ -127,19 +114,21 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
return acc;
}, {} as Record<string, string>);

return {
return buildMetadata({
title: current.metadata.title,
alternates: {
canonical: "/",
languages: metadataLanguages // Add custom metadata for languages
alternateLanguages: otherLanguages,
metadataLanguages,
language,
canonical: "/",
generateOpenGraphImage: {
url: `/api/opengraph-image/${slugUrl}`,
alt: `${current.metadata.title} OG Image`,
},
other: {
alternateLanguages: otherLanguages,
language
}
};
});
}

// catch every other source (nested sources)

const alternateLanguages = LanguageCodes.filter(lang => lang !== language).map(language => {
const slugPathTocheckForData = slugPaths.filter(path => path !== "data");
slugPathTocheckForData[1] = language;
Expand All @@ -157,17 +146,17 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
return acc;
}, {} as Record<string, string>);

return {
return buildMetadata({
title: current.metadata.title,
alternates: {
canonical: "/",
languages: metadataLanguages // Add custom metadata for languages
alternateLanguages,
metadataLanguages,
language,
canonical: "/",
generateOpenGraphImage: {
url: `/api/opengraph-image/${slugUrl}`,
alt: `${current.metadata.title} OG Image`,
},
other: {
alternateLanguages,
language
}
}
});
};

const page = ({ params }: { params: { slug: string[] } }) => {
Expand All @@ -181,8 +170,6 @@ const page = ({ params }: { params: { slug: string[] } }) => {
// catch language code followed by sources e.g ["es", "sources"]. English is omitted
const isRouteForLanguage = slug.length === 2 && OtherSupportedLanguages.includes(slug[0] as LanguageCode) && slug[1] === "sources";

// console.log({isRouteForLanguage})

if (isRouteForLanguage) {
const language = slug[0];
const sourcesAndLanguage = Object.keys(allSources);
Expand All @@ -200,9 +187,8 @@ const page = ({ params }: { params: { slug: string[] } }) => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Sources"
header='sources'
data={languageTreeArray}
description='Sources help you find transcripts within a specific talk, meetup, conference, and the likes.'
type='alphabet'
linkName='sources'
languageCode={language as LanguageCode}
Expand All @@ -222,19 +208,22 @@ const page = ({ params }: { params: { slug: string[] } }) => {
}
}

const slugPathsCopy = [...slugPaths].filter(item => item !== "data")
const language = slugPathsCopy.splice(1,1)[0] as LanguageCode

const metadata = current.metadata;
const data = loopArrOrObject(current?.data);
const isRoot = Array.isArray(current.data);
const { transcripts } = fetchTranscriptDetails(allTranscripts, data, isRoot);

const constructBackLink = () => {
const slugPathsCopy = [...slugPaths].filter(item => item !== "data")
const language = slugPathsCopy.splice(1,1)[0]
const indexPath = language === "en" ? "" : `/${language}`
const backRoute = slugPathsCopy.slice(0, -1).length ? slugPathsCopy.slice(0, -1).join("/") : ""
return backRoute ? `${indexPath}/${backRoute}` : `${indexPath}/sources`
}

const t = useTranslations(language);

return (
<div className="flex items-start lg:gap-[50px]">
<div className="flex flex-col w-full gap-6 md:gap-8 2xl:gap-10 no-scrollbar">
Expand All @@ -249,9 +238,9 @@ const page = ({ params }: { params: { slug: string[] } }) => {
<SourcesBreadCrumbs slugPaths={slugPaths} current={contentTree} />
</>
<div className='flex flex-col'>
<Link href={constructBackLink()} className='flex gap-1 items-center'>
<Link href={constructBackLink()} className='flex gap-1 items-center max-w-fit'>
<ArrowLinkRight className='rotate-180 w-5 md:w-6' />
<p>Back</p>
<p>{t("explore.back")}</p>
</Link>

<h3 className="text-xl 2xl:text-2xl font-medium pt-6 md:pt-3">
Expand Down
3 changes: 1 addition & 2 deletions src/app/(explore)/[languageCode]/categories/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ const CategoriesPage = ({params}: {params: { languageCode: LanguageCode}}) => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Categories"
header="categories"
data={reStructuredCategories}
description="Explore the main areas of focus within the Bitcoin technical ecosystem."
type="words"
linkName="tags"
languageCode={languageCode}
Expand Down
3 changes: 1 addition & 2 deletions src/app/(explore)/[languageCode]/speakers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ const SpeakerPage = ({ params }: { params: { languageCode: string } }) => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Speakers"
header="speakers"
data={speakers}
description="Discover insights from key figures of the Bitcoin community."
type="alphabet"
linkName="speakers"
languageCode={languageCode}
Expand Down
3 changes: 1 addition & 2 deletions src/app/(explore)/[languageCode]/topics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ const TopicsPage = ({ params }: { params: { languageCode: string } }) => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Topics"
header="topics"
data={topics}
description="Bitcoin is made up of an endless amount of topics, and there’s no shortage of rabbit holes to go down. "
type="alphabet"
linkName="tags"
languageCode={languageCode}
Expand Down
3 changes: 1 addition & 2 deletions src/app/(explore)/[languageCode]/types/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ const TypesPage = ({params}: {params: { languageCode: LanguageCode}}) => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Types"
header="types"
data={reStructuredTypes}
description="Sources tend to fall into discrete types, from podcasts to meetups. Find transcripts in your preferred format of communication."
type="words"
linkName="sources"
languageCode={languageCode}
Expand Down
4 changes: 2 additions & 2 deletions src/app/(explore)/categories/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const metadata: Metadata = {

const CategoriesPage = () => {
const languageCode = LanguageCode.en;

const categoriesTopic = (allCategoriesTopic as TopicsCategoryCountByLanguage)[
languageCode
].data;
Expand All @@ -45,9 +46,8 @@ const CategoriesPage = () => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Categories"
header="categories"
data={reStructuredCategories}
description="Explore the main areas of focus within the Bitcoin technical ecosystem."
type="words"
linkName="tags"
languageCode={languageCode}
Expand Down
3 changes: 1 addition & 2 deletions src/app/(explore)/sources/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ const SourcesPage = () => {
return (
<div className="flex flex-col text-black">
<TranscriptContentPage
header="Sources"
header="sources"
data={allSources}
description="Sources help you find transcripts within a specific talk, meetup, conference, and the likes."
type="alphabet"
linkName="sources"
languageCode={languageCode}
Expand Down
Loading

0 comments on commit 9c2095d

Please sign in to comment.