Skip to content

Commit 3b86286

Browse files
I18n locales translations (#85)
* 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
1 parent cc00c3a commit 3b86286

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1838
-644
lines changed

contentlayer.config.ts

+5-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
Source as ContentSourceType,
1818
} from "./.contentlayer/generated/types";
1919
import { LanguageCode, OtherSupportedLanguages } from "./src/config";
20+
import { getTopicTitle } from "./src/utils/topic";
2021

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

441+
const topicIndex = getTopics() as Topic[];
442+
440443
const topicsWithTitles = topics.map((topic) => {
441-
const currentTopic = getTopics().find(
442-
(topicData: FieldCountItem) => topicData.slug === topic
443-
);
444-
445-
if(currentTopic?.title && currentTopic?.title.includes("(Miscellaneous)")) {
446-
return {
447-
name: currentTopic?.title.replace("(Miscellaneous)",""),
448-
slug: currentTopic.slug,
449-
}
450-
}
451-
return {
452-
name: currentTopic?.title || topic,
453-
slug: currentTopic?.slug || topic,
454-
};
444+
const topicTitle = getTopicTitle(topic, topicIndex);
445+
return { name: topicTitle, slug: topic };
455446
});
456447
return topicsWithTitles;
457448
},

next.config.mjs

-32
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,10 @@ const nextConfig = {
44
rewrites: async () => {
55
return {
66
fallback: [
7-
// {
8-
// source: "/:path*.:ext([a-zA-Z0-9_+]{1,4})", // Match extensions that are 1-4 AlphaNumeric characters long
9-
// destination: "/gh-pages/:path*.:ext", // Rewrite to gh-pages/[path_here].ext
10-
// },
11-
// {
12-
// source: "/tags/:path",
13-
// destination: "/gh-pages/tags/:path/index.html",
14-
// },
15-
// {
16-
// source: "/speakers/:path",
17-
// destination: "/gh-pages/speakers/:path/index.html",
18-
// },
19-
// {
20-
// source: "/es",
21-
// destination: "/gh-pages/es/index.html",
22-
// },
23-
// {
24-
// source: "/zh",
25-
// destination: "/gh-pages/zh/index.html",
26-
// },
27-
// {
28-
// source: "/pt",
29-
// destination: "/gh-pages/pt/index.html",
30-
// },
31-
// {
32-
// source: "/topics",
33-
// destination: "/en/topics"
34-
// },
357
{
368
source: "/:path((?!.*\\.[a-zA-Z0-9]{1,4}$).*)", // Matches paths without a valid file extension
379
destination: "/transcript/:path*", // Rewrite to /transcripts/[path...]
3810
},
39-
// {
40-
// source: "/:path*",
41-
// destination: "/gh-pages/:path*/index.html",
42-
// },
4311
],
4412
};
4513
},

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"lint": "next lint"
1313
},
1414
"dependencies": {
15-
"@bitcoin-dev-project/bdp-ui": "^1.5.2",
15+
"@bitcoin-dev-project/bdp-ui": "^1.5.3",
1616
"@elastic/elasticsearch": "^8.16.2",
1717
"@tanstack/react-query": "^5.62.0",
1818
"@uiw/react-markdown-preview": "^5.1.3",

public/images/hero-desktop-image.webp

37.7 KB
Binary file not shown.

src/app/(explore)/[...slug]/page.tsx

+43-54
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import { ContentTreeArray, previewImageDimensions } from "@/utils/data";
66
import LinkIcon from "/public/svgs/link-icon.svg";
77
import WorldIcon from "/public/svgs/world-icon.svg";
88
import allSources from "@/public/sources-data.json";
9-
import allSourcesCount from "@/public/source-count-data.json";
109
import {
1110
constructSlugPaths,
11+
convertSlugToLanguageSlug,
1212
deriveAlternateLanguages,
1313
deriveSourcesList,
1414
fetchTranscriptDetails,
1515
loopArrOrObject,
16-
unsluggify,
1716
} from "@/utils";
1817
import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons";
1918
import {
@@ -28,6 +27,8 @@ import { LanguageCode, LanguageCodes, OtherSupportedLanguages } from "@/config";
2827
import { SourceTree } from "@/types";
2928
import { arrayWithoutElementAtIndex, traverseSourceTree } from "@/utils/sources";
3029
import { parseLanguageString } from "@/utils/locale";
30+
import useTranslations from "@/hooks/useTranslations";
31+
import { buildMetadata } from "@/utils/metadata";
3132

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

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

62+
const slugUrl = slug.join("/");
5963

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

62-
const isSourcesLanguageRoute = checkIfSourcesLanguageRoute(params.slug);
67+
const language = languagePath[0];
6368

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

6772
if (isSourcesLanguageRoute) {
68-
const languageCode = params.slug[0] as LanguageCode;
73+
const languageCode = languagePath[0] as LanguageCode;
6974
const { alternateLanguages, metadataLanguages } = deriveAlternateLanguages({
7075
languageCode,
7176
languages: LanguageCodes,
@@ -78,33 +83,16 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
7883
canonical: "/sources",
7984
languages: metadataLanguages // Add custom metadata for languages
8085
},
81-
openGraph:{
82-
images:[{
83-
url:`/api/og-image/${foundSource?.slug}`,
84-
width: previewImageDimensions.width,
85-
height: previewImageDimensions.height,
86-
alt: `${foundSource?.name} OG Image`
87-
}]
88-
},
89-
twitter:{
90-
images:[{
91-
url:`/api/og-image/${foundSource?.slug}`,
92-
width: previewImageDimensions.width,
93-
height: previewImageDimensions.height,
94-
alt: `${foundSource?.name} OG Image`
95-
}]
96-
},
9786
other: {
9887
alternateLanguages,
9988
language: languageCode
10089
}
10190
};
10291
}
10392

104-
105-
const slug = params.slug ?? [];
10693
const contentTree = allSources as SourceTree;
10794
const { slugPaths } = constructSlugPaths(slug);
95+
10896
let current: any = contentTree;
10997

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

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

130-
return {
117+
return buildMetadata({
131118
title: current.metadata.title,
132-
alternates: {
133-
canonical: "/",
134-
languages: metadataLanguages // Add custom metadata for languages
119+
alternateLanguages: otherLanguages,
120+
metadataLanguages,
121+
language,
122+
canonical: "/",
123+
generateOpenGraphImage: {
124+
url: `/api/opengraph-image/${slugUrl}`,
125+
alt: `${current.metadata.title} OG Image`,
135126
},
136-
other: {
137-
alternateLanguages: otherLanguages,
138-
language
139-
}
140-
};
127+
});
141128
}
142129

130+
// catch every other source (nested sources)
131+
143132
const alternateLanguages = LanguageCodes.filter(lang => lang !== language).map(language => {
144133
const slugPathTocheckForData = slugPaths.filter(path => path !== "data");
145134
slugPathTocheckForData[1] = language;
@@ -157,17 +146,17 @@ export const generateMetadata = async ({params}: { params: { slug: string[] } })
157146
return acc;
158147
}, {} as Record<string, string>);
159148

160-
return {
149+
return buildMetadata({
161150
title: current.metadata.title,
162-
alternates: {
163-
canonical: "/",
164-
languages: metadataLanguages // Add custom metadata for languages
151+
alternateLanguages,
152+
metadataLanguages,
153+
language,
154+
canonical: "/",
155+
generateOpenGraphImage: {
156+
url: `/api/opengraph-image/${slugUrl}`,
157+
alt: `${current.metadata.title} OG Image`,
165158
},
166-
other: {
167-
alternateLanguages,
168-
language
169-
}
170-
}
159+
});
171160
};
172161

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

184-
// console.log({isRouteForLanguage})
185-
186173
if (isRouteForLanguage) {
187174
const language = slug[0];
188175
const sourcesAndLanguage = Object.keys(allSources);
@@ -200,9 +187,8 @@ const page = ({ params }: { params: { slug: string[] } }) => {
200187
return (
201188
<div className="flex flex-col text-black">
202189
<TranscriptContentPage
203-
header="Sources"
190+
header='sources'
204191
data={languageTreeArray}
205-
description='Sources help you find transcripts within a specific talk, meetup, conference, and the likes.'
206192
type='alphabet'
207193
linkName='sources'
208194
languageCode={language as LanguageCode}
@@ -222,19 +208,22 @@ const page = ({ params }: { params: { slug: string[] } }) => {
222208
}
223209
}
224210

211+
const slugPathsCopy = [...slugPaths].filter(item => item !== "data")
212+
const language = slugPathsCopy.splice(1,1)[0] as LanguageCode
213+
225214
const metadata = current.metadata;
226215
const data = loopArrOrObject(current?.data);
227216
const isRoot = Array.isArray(current.data);
228217
const { transcripts } = fetchTranscriptDetails(allTranscripts, data, isRoot);
229218

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

225+
const t = useTranslations(language);
226+
238227
return (
239228
<div className="flex items-start lg:gap-[50px]">
240229
<div className="flex flex-col w-full gap-6 md:gap-8 2xl:gap-10 no-scrollbar">
@@ -249,9 +238,9 @@ const page = ({ params }: { params: { slug: string[] } }) => {
249238
<SourcesBreadCrumbs slugPaths={slugPaths} current={contentTree} />
250239
</>
251240
<div className='flex flex-col'>
252-
<Link href={constructBackLink()} className='flex gap-1 items-center'>
241+
<Link href={constructBackLink()} className='flex gap-1 items-center max-w-fit'>
253242
<ArrowLinkRight className='rotate-180 w-5 md:w-6' />
254-
<p>Back</p>
243+
<p>{t("explore.back")}</p>
255244
</Link>
256245

257246
<h3 className="text-xl 2xl:text-2xl font-medium pt-6 md:pt-3">

src/app/(explore)/[languageCode]/categories/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ const CategoriesPage = ({params}: {params: { languageCode: LanguageCode}}) => {
5757
return (
5858
<div className="flex flex-col text-black">
5959
<TranscriptContentPage
60-
header="Categories"
60+
header="categories"
6161
data={reStructuredCategories}
62-
description="Explore the main areas of focus within the Bitcoin technical ecosystem."
6362
type="words"
6463
linkName="tags"
6564
languageCode={languageCode}

src/app/(explore)/[languageCode]/speakers/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ const SpeakerPage = ({ params }: { params: { languageCode: string } }) => {
5656
return (
5757
<div className="flex flex-col text-black">
5858
<TranscriptContentPage
59-
header="Speakers"
59+
header="speakers"
6060
data={speakers}
61-
description="Discover insights from key figures of the Bitcoin community."
6261
type="alphabet"
6362
linkName="speakers"
6463
languageCode={languageCode}

src/app/(explore)/[languageCode]/topics/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ const TopicsPage = ({ params }: { params: { languageCode: string } }) => {
5656
return (
5757
<div className="flex flex-col text-black">
5858
<TranscriptContentPage
59-
header="Topics"
59+
header="topics"
6060
data={topics}
61-
description="Bitcoin is made up of an endless amount of topics, and there’s no shortage of rabbit holes to go down. "
6261
type="alphabet"
6362
linkName="tags"
6463
languageCode={languageCode}

src/app/(explore)/[languageCode]/types/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ const TypesPage = ({params}: {params: { languageCode: LanguageCode}}) => {
5858
return (
5959
<div className="flex flex-col text-black">
6060
<TranscriptContentPage
61-
header="Types"
61+
header="types"
6262
data={reStructuredTypes}
63-
description="Sources tend to fall into discrete types, from podcasts to meetups. Find transcripts in your preferred format of communication."
6463
type="words"
6564
linkName="sources"
6665
languageCode={languageCode}

src/app/(explore)/categories/page.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const metadata: Metadata = {
3131

3232
const CategoriesPage = () => {
3333
const languageCode = LanguageCode.en;
34+
3435
const categoriesTopic = (allCategoriesTopic as TopicsCategoryCountByLanguage)[
3536
languageCode
3637
].data;
@@ -45,9 +46,8 @@ const CategoriesPage = () => {
4546
return (
4647
<div className="flex flex-col text-black">
4748
<TranscriptContentPage
48-
header="Categories"
49+
header="categories"
4950
data={reStructuredCategories}
50-
description="Explore the main areas of focus within the Bitcoin technical ecosystem."
5151
type="words"
5252
linkName="tags"
5353
languageCode={languageCode}

src/app/(explore)/sources/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ const SourcesPage = () => {
2727
return (
2828
<div className="flex flex-col text-black">
2929
<TranscriptContentPage
30-
header="Sources"
30+
header="sources"
3131
data={allSources}
32-
description="Sources help you find transcripts within a specific talk, meetup, conference, and the likes."
3332
type="alphabet"
3433
linkName="sources"
3534
languageCode={languageCode}

0 commit comments

Comments
 (0)