From a47e678167fe5438515230e3167b128a816e0b2d Mon Sep 17 00:00:00 2001 From: Tony Tang Date: Sun, 4 Jan 2026 17:46:18 +0800 Subject: [PATCH] Merge pull request #1 from d0zingcat/feature/i18n_locale --- components/NotionPage.tsx | 122 +++++++++++-- lib/config.ts | 3 + lib/site-config.ts | 1 + lib/use-dark-mode.ts | 26 ++- next.config.js | 8 + package.json | 3 +- pnpm-lock.yaml | 357 +++++++++++++++++++++----------------- site.config.ts | 4 + 8 files changed, 344 insertions(+), 180 deletions(-) diff --git a/components/NotionPage.tsx b/components/NotionPage.tsx index 698ab1c..222c0df 100644 --- a/components/NotionPage.tsx +++ b/components/NotionPage.tsx @@ -131,6 +131,74 @@ const Modal = dynamic( } ) +// Helper function to filter recordMap by language +function filterRecordMapByLanguage( + recordMap: types.ExtendedRecordMap, + targetLang: string +): types.ExtendedRecordMap { + // Deep clone collection_query to avoid mutating the original + const newRecordMap: types.ExtendedRecordMap = { + ...recordMap, + collection_query: structuredClone( + recordMap.collection_query + ) as types.ExtendedRecordMap['collection_query'] + } + + for (const collectionId of Object.keys(newRecordMap.collection_query || {})) { + const collection = newRecordMap.collection[collectionId]?.value + if (!collection) continue + + const schema = collection.schema + if (!schema) continue + + const langPropId = Object.keys(schema).find( + (key) => schema[key]?.name?.toLowerCase() === 'language' + ) + + if (!langPropId) continue + + const views = newRecordMap.collection_query[collectionId] + if (!views) continue + + for (const viewId of Object.keys(views)) { + const view = views[viewId] + if (view?.collection_group_results?.blockIds) { + const originalBlockIds = view.collection_group_results.blockIds + const filteredBlockIds = originalBlockIds.filter((blockId: string) => { + const block = newRecordMap.block[blockId]?.value + if (!block) return false + + const propValue = block.properties?.[langPropId] + + // If no language property is set, show the post + if (!propValue) return true + + // Validate the property format + if ( + !Array.isArray(propValue) || + propValue.length === 0 || + !Array.isArray(propValue[0]) || + typeof propValue[0][0] !== 'string' + ) { + // If the language property is present but not in the expected format, + // treat it as if no specific language is set and keep the block. + return true + } + + const langText = propValue[0][0].toLowerCase().trim() + + // Show if language matches or is set to 'all' + return langText.includes(targetLang) || langText === 'all' + }) + + view.collection_group_results.blockIds = filteredBlockIds + } + } + } + + return newRecordMap +} + function Tweet({ id }: { id: string }) { const { recordMap } = useNotionContext() const tweet = (recordMap as types.ExtendedTweetRecordMap)?.tweets?.[id] @@ -192,6 +260,32 @@ export function NotionPage({ const router = useRouter() const lite = useSearchParam('lite') + // Use state to store filtered recordMap, initialized with original + const [filteredRecordMap, setFilteredRecordMap] = + React.useState(recordMap) + + // Apply language filtering only after mount to avoid hydration mismatch + React.useEffect(() => { + if (!config.isI18nEnabled || !recordMap) { + setFilteredRecordMap(recordMap) + return + } + + const browserLang = + typeof navigator !== 'undefined' && navigator.language + ? navigator.language + : 'en' + + if (!browserLang) { + setFilteredRecordMap(recordMap) + return + } + + const langCode = (browserLang.split('-')[0] || 'en').toLowerCase() + + setFilteredRecordMap(filterRecordMapByLanguage(recordMap, langCode)) + }, [recordMap, config.isI18nEnabled]) + const components = React.useMemo>( () => ({ nextLegacyImage: Image, @@ -220,11 +314,11 @@ export function NotionPage({ if (lite) params.lite = lite const searchParams = new URLSearchParams(params) - return site ? mapPageUrl(site, recordMap!, searchParams) : undefined - }, [site, recordMap, lite]) + return site ? mapPageUrl(site, filteredRecordMap!, searchParams) : undefined + }, [site, filteredRecordMap, lite]) - const keys = Object.keys(recordMap?.block || {}) - const block = recordMap?.block?.[keys[0]!]?.value + const keys = Object.keys(filteredRecordMap?.block || {}) + const block = filteredRecordMap?.block?.[keys[0]!]?.value // const isRootPage = // parsePageId(block?.id) === parsePageId(site?.rootNotionPageId) @@ -238,11 +332,11 @@ export function NotionPage({ () => ( ), - [block, recordMap, isBlogPost] + [block, filteredRecordMap, isBlogPost] ) const footer = React.useMemo(() =>