feat(i18n): expose localized text runtime primitive

Add a DOM-free LocalizedText component for MessageDescriptor values and export it from the runtime entry point.\n\nAlso expose provider detection so packages with a standalone fallback provider can avoid invoking Lingui hooks outside a provider. Cover localized rendering without an extra wrapper node.
This commit is contained in:
Maofeng
2026-07-31 15:06:58 +08:00
parent d36028d67b
commit 8d4d389982
4 changed files with 35 additions and 1 deletions
+9 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react" import * as React from "react"
import { useLingui } from "@lingui/react" import { LinguiContext, useLingui } from "@lingui/react"
import { I18nRuntimeContext } from "./context" import { I18nRuntimeContext } from "./context"
import { createIntlFormatters } from "./intl-formatters" import { createIntlFormatters } from "./intl-formatters"
@@ -26,6 +26,14 @@ export function useTranslate(): TranslateFunction {
) )
} }
/**
* Indicates whether the component is nested in an I18nProvider without
* invoking the provider-dependent Lingui hooks.
*/
export function useI18nProvider(): boolean {
return React.useContext(LinguiContext) !== null
}
export function useMessage(descriptor: MessageDescriptor): string export function useMessage(descriptor: MessageDescriptor): string
export function useMessage( export function useMessage(
id: MessageId, id: MessageId,
+2
View File
@@ -1,9 +1,11 @@
export { I18nProvider, type I18nProviderProps } from "./i18n-provider" export { I18nProvider, type I18nProviderProps } from "./i18n-provider"
export { LocalizedText, type LocalizedTextProps } from "./localized-text"
export { Translate, type TranslateProps } from "./translate" export { Translate, type TranslateProps } from "./translate"
export { createIntlFormatters } from "./intl-formatters" export { createIntlFormatters } from "./intl-formatters"
export { mergeMessageCatalogs, type MessageCatalogSource } from "./catalogs" export { mergeMessageCatalogs, type MessageCatalogSource } from "./catalogs"
export { export {
useFormatters, useFormatters,
useI18nProvider,
useLocale, useLocale,
useLocaleDefinition, useLocaleDefinition,
useLocales, useLocales,
@@ -0,0 +1,11 @@
import { useMessage } from "./hooks"
import type { MessageDescriptor } from "./types"
export interface LocalizedTextProps {
message: MessageDescriptor
}
/** Renders a message descriptor as localized text without adding a DOM node. */
export function LocalizedText({ message }: LocalizedTextProps) {
return useMessage(message)
}
@@ -13,6 +13,7 @@ import {
useMessage, useMessage,
useTranslate, useTranslate,
} from "./hooks" } from "./hooks"
import { LocalizedText } from "./localized-text"
import { Translate } from "./translate" import { Translate } from "./translate"
const catalogs = { const catalogs = {
@@ -96,6 +97,18 @@ describe("i18n runtime", () => {
expect(container.innerHTML).toBe("<p>Hello</p>") expect(container.innerHTML).toBe("<p>Hello</p>")
}) })
it("renders localized text without adding a DOM element", () => {
const { container } = render(
<I18nProvider catalogs={catalogs} locale="zh-Hans">
<p>
<LocalizedText message={{ id: "greeting", message: "Fallback" }} />
</p>
</I18nProvider>
)
expect(container.innerHTML).toBe("<p>你好</p>")
})
it("infers right-to-left locale direction", () => { it("infers right-to-left locale direction", () => {
function DirectionProbe() { function DirectionProbe() {
return <span>{useLocales()[0]?.direction}</span> return <span>{useLocales()[0]?.direction}</span>