@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server"
33
44import { DEFAULT_OG_IMAGE , SITE_URL } from "@/lib/constants"
55
6+ import { getTranslatedLocales } from "../i18n/translationRegistry"
7+
68import { isLocaleValidISO639_1 } from "./translations"
79import { getFullUrl } from "./url"
810
@@ -43,6 +45,7 @@ export const getMetadata = async ({
4345 image,
4446 author,
4547 noIndex = false ,
48+ translatedLocales,
4649} : {
4750 locale : string
4851 slug : string [ ]
@@ -52,36 +55,56 @@ export const getMetadata = async ({
5255 image ?: string
5356 author ?: string
5457 noIndex ?: boolean
58+ translatedLocales ?: string [ ]
5559} ) : Promise < Metadata > => {
5660 const slugString = slug . join ( "/" )
5761 const t = await getTranslations ( { locale, namespace : "common" } )
5862
5963 const description = descriptionProp || t ( "site-description" )
6064 const siteTitle = t ( "site-title" )
6165
62- // Set canonical URL w/ language path to avoid duplicate content
63- const url = getFullUrl ( locale , slugString )
66+ // Auto-detect translated locales if not provided
67+ const finalTranslatedLocales =
68+ translatedLocales ?? ( await getTranslatedLocales ( slugString ) )
69+
70+ const isCurrentPageTranslated = finalTranslatedLocales . includes ( locale )
6471
65- // Set x-default URL for hreflang
72+ // Set canonical URL
73+ // If current locale is NOT translated, set canonical to English version
74+ const canonicalLocale = isCurrentPageTranslated
75+ ? locale
76+ : routing . defaultLocale
77+ const url = getFullUrl ( canonicalLocale , slugString )
78+
79+ // Set x-default URL for hreflang (always use default locale)
6680 const xDefault = getFullUrl ( routing . defaultLocale , slugString )
6781
6882 /* Set fallback ogImage based on path */
6983 const ogImage = image || getOgImage ( slug )
7084
85+ // Only include hreflang alternates if the current page is translated
86+ // Untranslated pages should not have hreflang tags
87+ const localesForHreflang = isCurrentPageTranslated
88+ ? routing . locales . filter (
89+ ( loc ) =>
90+ finalTranslatedLocales . includes ( loc ) && isLocaleValidISO639_1 ( loc )
91+ )
92+ : [ ]
93+
7194 const base : Metadata = {
7295 title,
7396 description,
7497 metadataBase : new URL ( SITE_URL ) ,
7598 alternates : {
7699 canonical : url ,
77- languages : {
78- "x-default" : xDefault ,
79- ... Object . fromEntries (
80- routing . locales
81- . filter ( isLocaleValidISO639_1 )
82- . map ( ( locale ) => [ locale , getFullUrl ( locale , slugString ) ] )
83- ) ,
84- } ,
100+ ... ( localesForHreflang . length > 0 && {
101+ languages : {
102+ "x-default" : xDefault ,
103+ ... Object . fromEntries (
104+ localesForHreflang . map ( ( loc ) => [ loc , getFullUrl ( loc , slugString ) ] )
105+ ) ,
106+ } ,
107+ } ) ,
85108 } ,
86109 openGraph : {
87110 title,
@@ -117,5 +140,9 @@ export const getMetadata = async ({
117140 return { ...base , robots : { index : false } }
118141 }
119142
143+ if ( ! isCurrentPageTranslated ) {
144+ return { ...base , robots : { index : true , follow : true } }
145+ }
146+
120147 return base
121148}
0 commit comments