NextJS 13 - 如何使用路由组创建多语言未找到页面

Fab*_*tis 8 internationalization reactjs next.js next-i18next next.js13

我正在努力让本地化not-found页面与 NextJS 13 (app-dir) 一起使用。我正在遵循国际化的官方指南以及我在 GitHub 上找到的解决方案。

我尝试过的第一个解决方案

[locale]
  -- [...not-found]
       - page.tsx <- empty page, just calls notFound() (catch-all segment)
  -- (browse)
       - someFolder1 <- may multiple pages and maybe nested layouts
       - layout.tsx
  -- (public)
       - someFolder2
       - layout.tsx
  -- (private)
       - someFolder3
       - layout.tsx
  -- layout.tsx
  -- not-found.tsx <- should be served for all notFound() errors (including catch-all segment)
Run Code Online (Sandbox Code Playgroud)
此解决方案的问题

当我notFound从任何路由组调用该方法或访问不匹配的路由时;我收到一条错误消息:Unsupported Server Component type: Null。这看起来很奇怪,因为这两个文件肯定都有一个反应组件作为默认导出。

import { notFound } from "next/navigation";

export default function CatchAllUnmatched(): JSX.Element {
  notFound();
  return <></>;
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的另一个解决方案

我还尝试通过X-Language-Preference在中间件文件中添加的自定义属性向每个响应附加标头。在此过程中,我已将根布局移动为应用程序文件夹的直接后代,并检索区域设置,如下所示:

[locale]
  -- [...not-found]
       - page.tsx <- empty page, just calls notFound() (catch-all segment)
  -- (browse)
       - someFolder1 <- may multiple pages and maybe nested layouts
       - layout.tsx
  -- (public)
       - someFolder2
       - layout.tsx
  -- (private)
       - someFolder3
       - layout.tsx
  -- layout.tsx
  -- not-found.tsx <- should be served for all notFound() errors (including catch-all segment)
Run Code Online (Sandbox Code Playgroud)
此解决方案的问题

这种方法的问题是,例如lang,主 html 标签上的属性不会在客户端导航上重置。而且404页面只有在访问不匹配的路由时才会显示,调用该方法仍然会导致相同的错误。所以这个解决方案也是不可行的。/de/frnotFound

什么样的答案对我有帮助?

  • 显示工作国际化示例的真实世界示例的任何资源,包括多语言未找到页面,最好还使用具有嵌套布局的路由组。

  • Unsupported Server Component type: Null当两个文件肯定都有一个反应组件作为默认导出时,我遇到错误的可能原因。

如果您需要更多上下文、代码或任何关于一般设置不清楚的内容,只需发表评论,我将更新原始问题以满足要求。

Fab*_*tis 5

如果有人在类似的高级用例中遇到这个问题,我现在终于有了解决方案。使用第二种方法是解决这个问题的正确方法。

将根布局移出文件夹并定义一个包含所有 UI 的[locale]全局文件。not-found.tsx然后,您可以在布局内调用以下函数来检索区域设置:

export const getLocale = cache((): Language => {
  const preference = headers().get("X-Language-Preference");
  return (preference ?? fallbackLanguage) as Language;
});
Run Code Online (Sandbox Code Playgroud)

在您的中间件内部实现

向通过中间件传递的每个响应添加X-Language-Preference标头(或您想要的名称),在这里您可以定义自己的解析策略来检索应保存的区域设置。

const headerName = "X-Language-Preference";

function getRequestLocale(request: NextRequest) {
  // ...
}

export default async function middleware(request: NextRequest) {
  const locale = getRequestLocale(request);
  const response = NextResponse.next();

  response.headers.set(headerName, locale);
  return response;
}
Run Code Online (Sandbox Code Playgroud)

修复lang根布局组件中的标签

例如,在从 /de 到 /fr 的客户端导航上,主 html 标签上的 lang 属性不会重置

正如我在最初的问题中提到的。更新后此问题仍然存在,但我创建了一个名为的客户端组件,HTML该组件将从 URL 解析区域设置,或回退到 HTML 标记的方向和语言属性的默认区域设置。

interface Props extends React.HTMLAttributes<HTMLHtmlElement> {}

const HTML = memo(function HTML(props: Props): JSX.Element {
  const locale = useLocale();
  return <html lang={locale} dir={dir(locale)} {...props} />;
});

export default HTML;
export type { Props as HTMLProps };
Run Code Online (Sandbox Code Playgroud)

useLanguage钩子尝试从 useParams 钩子、usePathname 钩子解析区域设置,最后回退到默认区域设置。这是我使用过的实现:

export default function useLocale(): Language {
  const params = useParams();
  const pathname = usePathname();

  const localeFromParams = useMemo(() => {
    return params?.locale as Language | undefined;
  }, [params.locale]);

  const localeFromPathname = useMemo(() => {
    return pathname?.split?.("/")?.[1] as Language | undefined;
  }, [pathname]);

  const finalLocale = useMemo(() => {
    const decision = localeFromParams ?? localeFromPathname;
    if (!!decision && languages.includes(decision)) return decision;
    return fallbackLanguage;
  }, [localeFromParams, localeFromPathname]);

  return finalLocale;
}
Run Code Online (Sandbox Code Playgroud)

为什么这个方法突然奏效了呢?

更新到包含此 PR 的版本 v13.4.12 后,第二个解决方案突然开始工作,可能与错误或不需要的行为有关。