decantr 0.9.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/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decantr i18n — Internationalization module
|
|
3
|
+
*
|
|
4
|
+
* Reactive locale management, translations with interpolation,
|
|
5
|
+
* pluralization via Intl.PluralRules, RTL/LTR direction, and
|
|
6
|
+
* runtime message merging.
|
|
7
|
+
*
|
|
8
|
+
* Zero dependencies — uses only Decantr signals + built-in Intl APIs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSignal, createMemo } from '../state/index.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a dot-notation key against a nested message object.
|
|
15
|
+
* Example: resolve('nav.home', { nav: { home: 'Home' } }) => 'Home'
|
|
16
|
+
* @param {string} key - Dot-delimited key path
|
|
17
|
+
* @param {Record<string, any>} messages - Nested message object
|
|
18
|
+
* @returns {string|undefined}
|
|
19
|
+
*/
|
|
20
|
+
function resolve(key, messages) {
|
|
21
|
+
if (!messages || typeof messages !== 'object') return undefined;
|
|
22
|
+
const parts = key.split('.');
|
|
23
|
+
let current = messages;
|
|
24
|
+
for (let i = 0; i < parts.length; i++) {
|
|
25
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
26
|
+
current = current[parts[i]];
|
|
27
|
+
}
|
|
28
|
+
return typeof current === 'string' ? current : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Interpolate {param} placeholders in a template string.
|
|
33
|
+
* @param {string} template
|
|
34
|
+
* @param {Record<string, any>} [params]
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function interpolate(template, params) {
|
|
38
|
+
if (!params || typeof params !== 'object') return template;
|
|
39
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
40
|
+
return params[name] !== undefined ? String(params[name]) : match;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create an i18n instance with reactive locale tracking.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} config
|
|
48
|
+
* @param {string} config.locale - Initial locale (e.g. 'en')
|
|
49
|
+
* @param {Record<string, Record<string, any>>} config.messages - Messages keyed by locale
|
|
50
|
+
* @param {string} [config.fallbackLocale] - Fallback locale when key not found in current
|
|
51
|
+
* @returns {{
|
|
52
|
+
* t: (key: string, params?: Record<string, any>) => string,
|
|
53
|
+
* locale: () => string,
|
|
54
|
+
* setLocale: (locale: string) => void,
|
|
55
|
+
* setDirection: (dir: 'ltr' | 'rtl') => void,
|
|
56
|
+
* addMessages: (locale: string, messages: Record<string, any>) => void
|
|
57
|
+
* }}
|
|
58
|
+
*/
|
|
59
|
+
export function createI18n(config) {
|
|
60
|
+
if (!config || typeof config !== 'object') {
|
|
61
|
+
throw new Error('createI18n requires a config object');
|
|
62
|
+
}
|
|
63
|
+
if (typeof config.locale !== 'string' || !config.locale) {
|
|
64
|
+
throw new Error('createI18n requires a non-empty locale string');
|
|
65
|
+
}
|
|
66
|
+
if (!config.messages || typeof config.messages !== 'object') {
|
|
67
|
+
throw new Error('createI18n requires a messages object');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Deep-clone messages so mutations to the original don't leak in
|
|
71
|
+
const messageStore = {};
|
|
72
|
+
for (const loc of Object.keys(config.messages)) {
|
|
73
|
+
messageStore[loc] = deepClone(config.messages[loc]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const [locale, setLocaleSignal] = createSignal(config.locale);
|
|
77
|
+
const fallbackLocale = config.fallbackLocale || null;
|
|
78
|
+
|
|
79
|
+
// Internal version signal — bumped when addMessages is called
|
|
80
|
+
// so that memos/effects depending on t() re-evaluate.
|
|
81
|
+
const [version, setVersion] = createSignal(0);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Translate a key with optional interpolation and pluralization.
|
|
85
|
+
*
|
|
86
|
+
* Pluralization: if params.count is a number, appends a plural suffix
|
|
87
|
+
* (_zero, _one, _two, _few, _many, _other) using Intl.PluralRules.
|
|
88
|
+
* Falls back to the unsuffixed key if the plural form is not found.
|
|
89
|
+
*
|
|
90
|
+
* Fallback chain: current locale -> fallback locale -> raw key.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} key
|
|
93
|
+
* @param {Record<string, any>} [params]
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function t(key, params) {
|
|
97
|
+
// Read reactive dependencies so effects/memos track locale changes
|
|
98
|
+
const currentLocale = locale();
|
|
99
|
+
// Read version to re-evaluate when messages are added
|
|
100
|
+
version();
|
|
101
|
+
|
|
102
|
+
if (typeof key !== 'string') return '';
|
|
103
|
+
|
|
104
|
+
const currentMessages = messageStore[currentLocale];
|
|
105
|
+
const fallbackMessages = fallbackLocale ? messageStore[fallbackLocale] : null;
|
|
106
|
+
|
|
107
|
+
// Determine if pluralization is needed
|
|
108
|
+
let resolvedKey = key;
|
|
109
|
+
if (params && typeof params.count === 'number') {
|
|
110
|
+
const pluralSuffix = getPluralSuffix(currentLocale, params.count);
|
|
111
|
+
const pluralKey = key + '_' + pluralSuffix;
|
|
112
|
+
|
|
113
|
+
// Try plural key first in current locale, then fallback, then try base key
|
|
114
|
+
const pluralResult = resolve(pluralKey, currentMessages);
|
|
115
|
+
if (pluralResult !== undefined) {
|
|
116
|
+
return interpolate(pluralResult, params);
|
|
117
|
+
}
|
|
118
|
+
// Try plural key in fallback locale
|
|
119
|
+
if (fallbackMessages) {
|
|
120
|
+
const fallbackPlural = resolve(pluralKey, fallbackMessages);
|
|
121
|
+
if (fallbackPlural !== undefined) {
|
|
122
|
+
return interpolate(fallbackPlural, params);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Fall through to base key resolution
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Try current locale
|
|
129
|
+
const result = resolve(resolvedKey, currentMessages);
|
|
130
|
+
if (result !== undefined) {
|
|
131
|
+
return interpolate(result, params);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Try fallback locale
|
|
135
|
+
if (fallbackMessages) {
|
|
136
|
+
const fallbackResult = resolve(resolvedKey, fallbackMessages);
|
|
137
|
+
if (fallbackResult !== undefined) {
|
|
138
|
+
return interpolate(fallbackResult, params);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Return the key itself as last resort
|
|
143
|
+
return key;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Set the active locale.
|
|
148
|
+
* @param {string} loc
|
|
149
|
+
*/
|
|
150
|
+
function setLocale(loc) {
|
|
151
|
+
if (typeof loc !== 'string' || !loc) return;
|
|
152
|
+
setLocaleSignal(loc);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Set document direction (LTR/RTL).
|
|
157
|
+
* @param {'ltr' | 'rtl'} dir
|
|
158
|
+
*/
|
|
159
|
+
function setDirection(dir) {
|
|
160
|
+
if (dir !== 'ltr' && dir !== 'rtl') return;
|
|
161
|
+
if (typeof document !== 'undefined' && document.documentElement) {
|
|
162
|
+
document.documentElement.setAttribute('dir', dir);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Add or merge messages for a locale at runtime.
|
|
168
|
+
* @param {string} loc - Target locale
|
|
169
|
+
* @param {Record<string, any>} messages - Messages to merge
|
|
170
|
+
*/
|
|
171
|
+
function addMessages(loc, messages) {
|
|
172
|
+
if (typeof loc !== 'string' || !loc) return;
|
|
173
|
+
if (!messages || typeof messages !== 'object') return;
|
|
174
|
+
|
|
175
|
+
if (!messageStore[loc]) {
|
|
176
|
+
messageStore[loc] = {};
|
|
177
|
+
}
|
|
178
|
+
deepMerge(messageStore[loc], messages);
|
|
179
|
+
// Bump version so reactive consumers re-evaluate
|
|
180
|
+
setVersion(v => v + 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
t,
|
|
185
|
+
locale,
|
|
186
|
+
setLocale,
|
|
187
|
+
setDirection,
|
|
188
|
+
addMessages
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Internal Helpers ─────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the plural category suffix for a given locale and count.
|
|
196
|
+
* Uses Intl.PluralRules when available, falls back to basic logic.
|
|
197
|
+
* @param {string} locale
|
|
198
|
+
* @param {number} count
|
|
199
|
+
* @returns {string} - 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'
|
|
200
|
+
*/
|
|
201
|
+
function getPluralSuffix(locale, count) {
|
|
202
|
+
if (typeof Intl !== 'undefined' && Intl.PluralRules) {
|
|
203
|
+
try {
|
|
204
|
+
const rules = new Intl.PluralRules(locale);
|
|
205
|
+
return rules.select(count);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// Invalid locale — fall through to basic logic
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Basic fallback: English-like pluralization
|
|
211
|
+
if (count === 0) return 'zero';
|
|
212
|
+
if (count === 1) return 'one';
|
|
213
|
+
return 'other';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Deep-clone a plain object/value.
|
|
218
|
+
* @param {any} obj
|
|
219
|
+
* @returns {any}
|
|
220
|
+
*/
|
|
221
|
+
function deepClone(obj) {
|
|
222
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
223
|
+
if (Array.isArray(obj)) return obj.map(deepClone);
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const key of Object.keys(obj)) {
|
|
226
|
+
result[key] = deepClone(obj[key]);
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Deep-merge source into target, mutating target in place.
|
|
233
|
+
* @param {Record<string, any>} target
|
|
234
|
+
* @param {Record<string, any>} source
|
|
235
|
+
*/
|
|
236
|
+
function deepMerge(target, source) {
|
|
237
|
+
for (const key of Object.keys(source)) {
|
|
238
|
+
if (
|
|
239
|
+
source[key] !== null &&
|
|
240
|
+
typeof source[key] === 'object' &&
|
|
241
|
+
!Array.isArray(source[key]) &&
|
|
242
|
+
target[key] !== null &&
|
|
243
|
+
typeof target[key] === 'object' &&
|
|
244
|
+
!Array.isArray(target[key])
|
|
245
|
+
) {
|
|
246
|
+
deepMerge(target[key], source[key]);
|
|
247
|
+
} else {
|
|
248
|
+
target[key] = deepClone(source[key]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|