@arqel-dev/i18n 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +18 -1
- package/dist/index.js +23 -25
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -58,7 +58,24 @@ type LocaleSwitcherProps = {
|
|
|
58
58
|
*/
|
|
59
59
|
declare function LocaleSwitcher({ endpoint, label, labels, className, }: LocaleSwitcherProps): ReactElement;
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Builds a translator bound to `dict`, delegating to the SAME pluralization-
|
|
63
|
+
* and locale-aware `translate()` core that `@arqel-dev/react`'s
|
|
64
|
+
* `useArqelTranslations()` uses. This keeps the provider's `t()` and the hook
|
|
65
|
+
* in lock-step: one page, one dictionary, one resolution semantics.
|
|
66
|
+
*
|
|
67
|
+
* Behaviour mirrors Laravel's `__()`:
|
|
68
|
+
* - dotted-path lookup (`actions.create` → `dict.actions.create`);
|
|
69
|
+
* - missing keys return the key itself (visible during development);
|
|
70
|
+
* - `:placeholder` tokens are substituted from `params`;
|
|
71
|
+
* - when `params.count` is a number the value is treated as a pluralizable
|
|
72
|
+
* string (`{one} :count item|{other} :count items`) and the matching form is
|
|
73
|
+
* selected via `Intl.PluralRules` in `locale` before substitution.
|
|
74
|
+
*
|
|
75
|
+
* `locale` should be a BCP-47 tag (`pt-BR`); pass the active panel locale so
|
|
76
|
+
* pluralization matches the rendered language.
|
|
77
|
+
*/
|
|
78
|
+
declare function buildTranslator(dict: TranslationDictionary, locale?: string): TranslateFn;
|
|
62
79
|
|
|
63
80
|
/**
|
|
64
81
|
* `useTranslation()` retorna o tradutor já bound ao locale ativo do
|
package/dist/index.js
CHANGED
|
@@ -3,30 +3,19 @@ import { usePage } from "@inertiajs/react";
|
|
|
3
3
|
import { createContext, useContext, useMemo } from "react";
|
|
4
4
|
|
|
5
5
|
// src/translate.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let cursor = dict;
|
|
9
|
-
for (const segment of segments) {
|
|
10
|
-
if (cursor === void 0 || typeof cursor === "string") {
|
|
11
|
-
return void 0;
|
|
12
|
-
}
|
|
13
|
-
cursor = cursor[segment];
|
|
14
|
-
}
|
|
15
|
-
return typeof cursor === "string" ? cursor : void 0;
|
|
16
|
-
}
|
|
17
|
-
function interpolate(template, params) {
|
|
18
|
-
if (params === void 0) {
|
|
19
|
-
return template;
|
|
20
|
-
}
|
|
21
|
-
return template.replace(/:([a-zA-Z0-9_]+)/g, (match, name) => {
|
|
22
|
-
const value = params[name];
|
|
23
|
-
return value === void 0 ? match : String(value);
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
function buildTranslator(dict) {
|
|
6
|
+
import { translate as coreTranslate } from "@arqel-dev/react/utils";
|
|
7
|
+
function buildTranslator(dict, locale) {
|
|
27
8
|
return (key, params) => {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
9
|
+
const count = typeof params?.["count"] === "number" ? params["count"] : void 0;
|
|
10
|
+
return coreTranslate(
|
|
11
|
+
dict,
|
|
12
|
+
key,
|
|
13
|
+
params ? {
|
|
14
|
+
replacements: params,
|
|
15
|
+
...locale !== void 0 ? { locale } : {},
|
|
16
|
+
...count !== void 0 ? { count } : {}
|
|
17
|
+
} : void 0
|
|
18
|
+
);
|
|
30
19
|
};
|
|
31
20
|
}
|
|
32
21
|
|
|
@@ -49,7 +38,10 @@ function I18nProvider({
|
|
|
49
38
|
locale: resolved.locale,
|
|
50
39
|
available: resolved.available,
|
|
51
40
|
translations: resolved.translations,
|
|
52
|
-
|
|
41
|
+
// Thread the active locale (normalized to BCP-47) so the translator's
|
|
42
|
+
// pluralization matches the rendered language and agrees with
|
|
43
|
+
// `useArqelTranslations()`.
|
|
44
|
+
t: buildTranslator(resolved.translations, resolved.locale.replace(/_/g, "-"))
|
|
53
45
|
}),
|
|
54
46
|
[resolved.locale, resolved.available, resolved.translations]
|
|
55
47
|
);
|
|
@@ -87,7 +79,7 @@ import {
|
|
|
87
79
|
SelectValue
|
|
88
80
|
} from "@arqel-dev/ui";
|
|
89
81
|
import { router } from "@inertiajs/react";
|
|
90
|
-
import { useId } from "react";
|
|
82
|
+
import { useEffect, useId } from "react";
|
|
91
83
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
92
84
|
var DEFAULT_LABELS = {
|
|
93
85
|
en: "English",
|
|
@@ -106,6 +98,12 @@ function LocaleSwitcher({
|
|
|
106
98
|
const selectId = useId();
|
|
107
99
|
const fieldLabel = label ?? t("arqel.locale.switch");
|
|
108
100
|
const map = { ...DEFAULT_LABELS, ...labels ?? {} };
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (typeof document === "undefined") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
document.documentElement.lang = locale.replace(/_/g, "-");
|
|
106
|
+
}, [locale]);
|
|
109
107
|
const handleValueChange = (next) => {
|
|
110
108
|
if (next === locale) {
|
|
111
109
|
return;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/I18nProvider.tsx","../src/translate.ts","../src/LocaleSwitcher.tsx","../src/useTranslation.ts"],"sourcesContent":["import { usePage } from '@inertiajs/react';\nimport { createContext, type ReactElement, type ReactNode, useContext, useMemo } from 'react';\nimport { buildTranslator } from './translate';\nimport type { I18nContextValue, I18nSharedProps } from './types';\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\ntype I18nProviderProps = {\n children: ReactNode;\n /**\n * Override the shared-prop `i18n` payload (useful in tests or in\n * non-Inertia hosts). When omitted, falls back to\n * `usePage().props.i18n`.\n */\n i18n?: I18nSharedProps;\n /** Fallback locale when neither `i18n` nor Inertia shared props supply one. */\n fallbackLocale?: string;\n};\n\n/**\n * Provider que torna `useTranslation()` disponível abaixo. Lê os dados\n * de `usePage().props.i18n` injetados pelo middleware PHP (ver\n * `Arqel\\Core\\Http\\Middleware\\HandleArqelInertiaRequests`). Em testes\n * ou em ambientes sem Inertia, a prop `i18n` pode ser passada\n * diretamente.\n */\nexport function I18nProvider({\n children,\n i18n,\n fallbackLocale = 'en',\n}: I18nProviderProps): ReactElement {\n const sharedI18n = useSharedI18n();\n const resolved: I18nSharedProps = i18n ??\n sharedI18n ?? {\n locale: fallbackLocale,\n available: [fallbackLocale],\n translations: {},\n };\n\n const value = useMemo<I18nContextValue>(\n () => ({\n locale: resolved.locale,\n available: resolved.available,\n translations: resolved.translations,\n t: buildTranslator(resolved.translations),\n }),\n [resolved.locale, resolved.available, resolved.translations],\n );\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\nfunction useSharedI18n(): I18nSharedProps | undefined {\n // `usePage` is always called unconditionally to honour Rules of\n // Hooks. When Inertia is not bootstrapped, our test mock returns\n // `{ props: {} }` so the lookup just yields `undefined` here.\n const page = usePage();\n const i18n = (page?.props as Record<string, unknown> | undefined)?.['i18n'];\n return isI18nSharedProps(i18n) ? i18n : undefined;\n}\n\nfunction isI18nSharedProps(value: unknown): value is I18nSharedProps {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n typeof v['locale'] === 'string' &&\n Array.isArray(v['available']) &&\n typeof v['translations'] === 'object' &&\n v['translations'] !== null\n );\n}\n\nexport function useI18nContext(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (ctx === null) {\n throw new Error(\n '[@arqel-dev/i18n] useTranslation/useI18n must be used inside an <I18nProvider>.',\n );\n }\n return ctx;\n}\n","import type { TranslateFn, TranslationDictionary } from './types';\n\n/**\n * Walks the dotted-path `key` through `dict`, returning the leaf string\n * or — when missing — the original key. This mirrors Laravel's `__()`\n * fallback behaviour, keeping missing keys visible during development\n * without throwing in production.\n */\nfunction lookup(dict: TranslationDictionary, key: string): string | undefined {\n const segments = key.split('.');\n let cursor: string | TranslationDictionary | undefined = dict;\n for (const segment of segments) {\n if (cursor === undefined || typeof cursor === 'string') {\n return undefined;\n }\n cursor = cursor[segment];\n }\n return typeof cursor === 'string' ? cursor : undefined;\n}\n\n/**\n * Replaces `:placeholder` tokens with values from `params`. Numeric\n * values are coerced to strings — keep the API symmetric with Laravel\n * `__('greeting', ['name' => $name])`.\n */\nfunction interpolate(template: string, params?: Readonly<Record<string, string | number>>): string {\n if (params === undefined) {\n return template;\n }\n return template.replace(/:([a-zA-Z0-9_]+)/g, (match, name: string) => {\n const value = params[name];\n return value === undefined ? match : String(value);\n });\n}\n\nexport function buildTranslator(dict: TranslationDictionary): TranslateFn {\n return (key, params) => {\n const found = lookup(dict, key);\n return found === undefined ? key : interpolate(found, params);\n };\n}\n","import {\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@arqel-dev/ui';\nimport { router } from '@inertiajs/react';\nimport { type ReactElement, useId } from 'react';\nimport { useI18nContext } from './I18nProvider';\n\ntype LocaleSwitcherProps = {\n /** Endpoint POST que persiste o locale na sessão (default `/admin/locale`). */\n endpoint?: string;\n /** Label visível acima do select (default usa `t('arqel.locale.switch')`). */\n label?: string;\n /** Override do mapa locale → display name. */\n labels?: Readonly<Record<string, string>>;\n className?: string;\n};\n\nconst DEFAULT_LABELS: Readonly<Record<string, string>> = {\n en: 'English',\n pt_BR: 'Português (BR)',\n 'pt-BR': 'Português (BR)',\n es: 'Español',\n fr: 'Français',\n};\n\n/**\n * `<LocaleSwitcher />` — Radix Select styled (shadcn) que dispara POST\n * para `endpoint` com `{locale}`, aproveitando Inertia router para\n * preservar state e re-renderizar com o novo locale resolvido.\n */\nexport function LocaleSwitcher({\n endpoint = '/admin/locale',\n label,\n labels,\n className,\n}: LocaleSwitcherProps): ReactElement {\n const { locale, available, t } = useI18nContext();\n const selectId = useId();\n const fieldLabel = label ?? t('arqel.locale.switch');\n const map = { ...DEFAULT_LABELS, ...(labels ?? {}) };\n\n const handleValueChange = (next: string): void => {\n if (next === locale) {\n return;\n }\n router.post(endpoint, { locale: next }, { preserveScroll: true, preserveState: true });\n };\n\n return (\n <div className={className} data-arqel-locale-switcher=\"\">\n <Label htmlFor={selectId} className=\"text-xs text-muted-foreground mb-1 block\">\n {fieldLabel}\n </Label>\n <Select value={locale} onValueChange={handleValueChange}>\n <SelectTrigger id={selectId} aria-label={fieldLabel}>\n <SelectValue placeholder={fieldLabel} />\n </SelectTrigger>\n <SelectContent>\n {available.map((code) => (\n <SelectItem key={code} value={code}>\n {map[code] ?? code}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n","import { useI18nContext } from './I18nProvider';\nimport type { TranslateFn } from './types';\n\n/**\n * `useTranslation()` retorna o tradutor já bound ao locale ativo do\n * provider. Mirror compacto da assinatura do Laravel `__()`:\n *\n * ```tsx\n * const t = useTranslation();\n * t('actions.save'); // → \"Save\" / \"Salvar\"\n * t('messages.welcome', { name: 'Diogo' });\n * ```\n */\nexport function useTranslation(): TranslateFn {\n return useI18nContext().t;\n}\n\nexport function useI18n() {\n const { t, locale, available, translations } = useI18nContext();\n return { t, locale, available, translations };\n}\n"],"mappings":"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","names":["jsx"]}
|
|
1
|
+
{"version":3,"sources":["../src/I18nProvider.tsx","../src/translate.ts","../src/LocaleSwitcher.tsx","../src/useTranslation.ts"],"sourcesContent":["import { usePage } from '@inertiajs/react';\nimport { createContext, type ReactElement, type ReactNode, useContext, useMemo } from 'react';\nimport { buildTranslator } from './translate';\nimport type { I18nContextValue, I18nSharedProps } from './types';\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\ntype I18nProviderProps = {\n children: ReactNode;\n /**\n * Override the shared-prop `i18n` payload (useful in tests or in\n * non-Inertia hosts). When omitted, falls back to\n * `usePage().props.i18n`.\n */\n i18n?: I18nSharedProps;\n /** Fallback locale when neither `i18n` nor Inertia shared props supply one. */\n fallbackLocale?: string;\n};\n\n/**\n * Provider que torna `useTranslation()` disponível abaixo. Lê os dados\n * de `usePage().props.i18n` injetados pelo middleware PHP (ver\n * `Arqel\\Core\\Http\\Middleware\\HandleArqelInertiaRequests`). Em testes\n * ou em ambientes sem Inertia, a prop `i18n` pode ser passada\n * diretamente.\n */\nexport function I18nProvider({\n children,\n i18n,\n fallbackLocale = 'en',\n}: I18nProviderProps): ReactElement {\n const sharedI18n = useSharedI18n();\n const resolved: I18nSharedProps = i18n ??\n sharedI18n ?? {\n locale: fallbackLocale,\n available: [fallbackLocale],\n translations: {},\n };\n\n const value = useMemo<I18nContextValue>(\n () => ({\n locale: resolved.locale,\n available: resolved.available,\n translations: resolved.translations,\n // Thread the active locale (normalized to BCP-47) so the translator's\n // pluralization matches the rendered language and agrees with\n // `useArqelTranslations()`.\n t: buildTranslator(resolved.translations, resolved.locale.replace(/_/g, '-')),\n }),\n [resolved.locale, resolved.available, resolved.translations],\n );\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\nfunction useSharedI18n(): I18nSharedProps | undefined {\n // `usePage` is always called unconditionally to honour Rules of\n // Hooks. When Inertia is not bootstrapped, our test mock returns\n // `{ props: {} }` so the lookup just yields `undefined` here.\n const page = usePage();\n const i18n = (page?.props as Record<string, unknown> | undefined)?.['i18n'];\n return isI18nSharedProps(i18n) ? i18n : undefined;\n}\n\nfunction isI18nSharedProps(value: unknown): value is I18nSharedProps {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n typeof v['locale'] === 'string' &&\n Array.isArray(v['available']) &&\n typeof v['translations'] === 'object' &&\n v['translations'] !== null\n );\n}\n\nexport function useI18nContext(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (ctx === null) {\n throw new Error(\n '[@arqel-dev/i18n] useTranslation/useI18n must be used inside an <I18nProvider>.',\n );\n }\n return ctx;\n}\n","import { translate as coreTranslate } from '@arqel-dev/react/utils';\nimport type { TranslateFn, TranslationDictionary } from './types';\n\n/**\n * Builds a translator bound to `dict`, delegating to the SAME pluralization-\n * and locale-aware `translate()` core that `@arqel-dev/react`'s\n * `useArqelTranslations()` uses. This keeps the provider's `t()` and the hook\n * in lock-step: one page, one dictionary, one resolution semantics.\n *\n * Behaviour mirrors Laravel's `__()`:\n * - dotted-path lookup (`actions.create` → `dict.actions.create`);\n * - missing keys return the key itself (visible during development);\n * - `:placeholder` tokens are substituted from `params`;\n * - when `params.count` is a number the value is treated as a pluralizable\n * string (`{one} :count item|{other} :count items`) and the matching form is\n * selected via `Intl.PluralRules` in `locale` before substitution.\n *\n * `locale` should be a BCP-47 tag (`pt-BR`); pass the active panel locale so\n * pluralization matches the rendered language.\n */\nexport function buildTranslator(dict: TranslationDictionary, locale?: string): TranslateFn {\n return (key, params) => {\n const count = typeof params?.['count'] === 'number' ? params['count'] : undefined;\n return coreTranslate(\n dict as Record<string, unknown>,\n key,\n params\n ? {\n replacements: params,\n ...(locale !== undefined ? { locale } : {}),\n ...(count !== undefined ? { count } : {}),\n }\n : undefined,\n );\n };\n}\n","import {\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@arqel-dev/ui';\nimport { router } from '@inertiajs/react';\nimport { type ReactElement, useEffect, useId } from 'react';\nimport { useI18nContext } from './I18nProvider';\n\ntype LocaleSwitcherProps = {\n /** Endpoint POST que persiste o locale na sessão (default `/admin/locale`). */\n endpoint?: string;\n /** Label visível acima do select (default usa `t('arqel.locale.switch')`). */\n label?: string;\n /** Override do mapa locale → display name. */\n labels?: Readonly<Record<string, string>>;\n className?: string;\n};\n\nconst DEFAULT_LABELS: Readonly<Record<string, string>> = {\n en: 'English',\n pt_BR: 'Português (BR)',\n 'pt-BR': 'Português (BR)',\n es: 'Español',\n fr: 'Français',\n};\n\n/**\n * `<LocaleSwitcher />` — Radix Select styled (shadcn) que dispara POST\n * para `endpoint` com `{locale}`, aproveitando Inertia router para\n * preservar state e re-renderizar com o novo locale resolvido.\n */\nexport function LocaleSwitcher({\n endpoint = '/admin/locale',\n label,\n labels,\n className,\n}: LocaleSwitcherProps): ReactElement {\n const { locale, available, t } = useI18nContext();\n const selectId = useId();\n const fieldLabel = label ?? t('arqel.locale.switch');\n const map = { ...DEFAULT_LABELS, ...(labels ?? {}) };\n\n // After a client-side locale change the Inertia visit swaps the page\n // component without re-rendering the Blade shell that set `<html lang>`,\n // leaving the attribute stale (screen readers keep the old pronunciation).\n // Sync it from the active locale, normalizing the underscore Laravel tag\n // (`pt_BR`) to BCP-47 (`pt-BR`) so `lang` is a valid language tag.\n useEffect(() => {\n if (typeof document === 'undefined') {\n return;\n }\n document.documentElement.lang = locale.replace(/_/g, '-');\n }, [locale]);\n\n const handleValueChange = (next: string): void => {\n if (next === locale) {\n return;\n }\n router.post(endpoint, { locale: next }, { preserveScroll: true, preserveState: true });\n };\n\n return (\n <div className={className} data-arqel-locale-switcher=\"\">\n <Label htmlFor={selectId} className=\"text-xs text-muted-foreground mb-1 block\">\n {fieldLabel}\n </Label>\n <Select value={locale} onValueChange={handleValueChange}>\n <SelectTrigger id={selectId} aria-label={fieldLabel}>\n <SelectValue placeholder={fieldLabel} />\n </SelectTrigger>\n <SelectContent>\n {available.map((code) => (\n <SelectItem key={code} value={code}>\n {map[code] ?? code}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n","import { useI18nContext } from './I18nProvider';\nimport type { TranslateFn } from './types';\n\n/**\n * `useTranslation()` retorna o tradutor já bound ao locale ativo do\n * provider. Mirror compacto da assinatura do Laravel `__()`:\n *\n * ```tsx\n * const t = useTranslation();\n * t('actions.save'); // → \"Save\" / \"Salvar\"\n * t('messages.welcome', { name: 'Diogo' });\n * ```\n */\nexport function useTranslation(): TranslateFn {\n return useI18nContext().t;\n}\n\nexport function useI18n() {\n const { t, locale, available, translations } = useI18nContext();\n return { t, locale, available, translations };\n}\n"],"mappings":"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","names":["jsx"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arqel-dev/i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Internationalization primitives for Arqel admin panels (Inertia-driven, no runtime deps).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://arqel.dev",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"@inertiajs/react": "^3.0.0",
|
|
35
35
|
"react": "^19.2.0",
|
|
36
36
|
"react-dom": "^19.2.0",
|
|
37
|
-
"@arqel-dev/
|
|
37
|
+
"@arqel-dev/react": "0.16.0",
|
|
38
|
+
"@arqel-dev/ui": "0.16.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@inertiajs/core": "^3.0.0",
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
"tsup": "^8.5.0",
|
|
50
51
|
"typescript": "^5.6.3",
|
|
51
52
|
"vitest": "^4.1.0",
|
|
52
|
-
"@arqel-dev/
|
|
53
|
+
"@arqel-dev/react": "0.16.0",
|
|
54
|
+
"@arqel-dev/ui": "0.16.0"
|
|
53
55
|
},
|
|
54
56
|
"publishConfig": {
|
|
55
57
|
"access": "public"
|