@0001-labs/design-system-1 0.0.0-stage → 0.3.0-alpha.13
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/DS1/0-face/control.ts +99 -0
- package/DS1/0-face/define.ts +5 -0
- package/DS1/0-face/device.ts +414 -0
- package/DS1/0-face/grid-fit.ts +63 -0
- package/DS1/0-face/grid.ts +144 -0
- package/DS1/0-face/i18n.ts +1023 -0
- package/DS1/0-face/language-routing.ts +135 -0
- package/DS1/0-face/language-validator.ts +46 -0
- package/DS1/0-face/languages.ts +242 -0
- package/DS1/0-face/layout-inspector.ts +184 -0
- package/DS1/0-face/preferences.ts +39 -0
- package/DS1/0-face/pricing.ts +57 -0
- package/DS1/0-face/scaling.ts +25 -0
- package/DS1/0-face/shared-words.ts +45 -0
- package/DS1/0-face/theme.ts +58 -0
- package/DS1/0-face/tooltips.ts +82 -0
- package/DS1/0-face/translations/da.json +63 -0
- package/DS1/0-face/translations/de.json +63 -0
- package/DS1/0-face/translations/en.json +63 -0
- package/DS1/0-face/translations/es.json +63 -0
- package/DS1/0-face/translations/fi.json +63 -0
- package/DS1/0-face/translations/fr.json +63 -0
- package/DS1/0-face/translations/is.json +56 -0
- package/DS1/0-face/translations/it.json +63 -0
- package/DS1/0-face/translations/ja.json +63 -0
- package/DS1/0-face/translations/ko.json +63 -0
- package/DS1/0-face/translations/nb.json +63 -0
- package/DS1/0-face/translations/pt-BR.json +63 -0
- package/DS1/0-face/translations/pt.json +63 -0
- package/DS1/0-face/translations/sv.json +63 -0
- package/DS1/0-face/translations/zh.json +63 -0
- package/DS1/0-face/translations-format.ts +216 -0
- package/DS1/1-root/component-appearance.css +32 -0
- package/DS1/1-root/component-metrics.css +21 -0
- package/DS1/1-root/component-tokens.css +90 -0
- package/DS1/1-root/controls.css +261 -0
- package/DS1/1-root/fonts/GT-America-Compressed-Regular.woff2 +0 -0
- package/DS1/1-root/fonts/GT-America-Standard-Medium.woff2 +0 -0
- package/DS1/1-root/fonts/GT-America-Standard-Regular.woff2 +0 -0
- package/DS1/1-root/fonts/GT-Canon-M-Standard-Medium-Trial.woff2 +0 -0
- package/DS1/1-root/fonts/Inter-Arrows.woff2 +0 -0
- package/DS1/1-root/fonts/Inter-LICENSE.md +7 -0
- package/DS1/1-root/fonts/Iosevka-LICENSE.md +7 -0
- package/DS1/1-root/fonts/Iosevka-Regular.woff2 +0 -0
- package/DS1/1-root/grid.css +31 -0
- package/DS1/1-root/one.css +602 -0
- package/DS1/1-root/one.semantic.css +683 -0
- package/DS1/1-root/palette.ts +91 -0
- package/DS1/2-core/ds-avatar.ts +130 -0
- package/DS1/2-core/ds-badge.ts +77 -0
- package/DS1/2-core/ds-banner.ts +122 -0
- package/DS1/2-core/ds-button.ts +163 -0
- package/DS1/2-core/ds-card.ts +137 -0
- package/DS1/2-core/ds-checkbox.ts +14 -0
- package/DS1/2-core/ds-color.ts +32 -0
- package/DS1/2-core/ds-composer.ts +182 -0
- package/DS1/2-core/ds-cycle.ts +628 -0
- package/DS1/2-core/ds-date.ts +24 -0
- package/DS1/2-core/ds-file-picker.ts +83 -0
- package/DS1/2-core/ds-gap.ts +34 -0
- package/DS1/2-core/ds-icon.ts +161 -0
- package/DS1/2-core/ds-input.ts +518 -0
- package/DS1/2-core/ds-language.ts +94 -0
- package/DS1/2-core/ds-pagedots.ts +52 -0
- package/DS1/2-core/ds-radio.ts +25 -0
- package/DS1/2-core/ds-select.ts +44 -0
- package/DS1/2-core/ds-split.ts +122 -0
- package/DS1/2-core/ds-swatch.ts +20 -0
- package/DS1/2-core/ds-switch.ts +13 -0
- package/DS1/2-core/ds-text.ts +190 -0
- package/DS1/2-core/ds-textarea.ts +136 -0
- package/DS1/2-core/ds-token.ts +40 -0
- package/DS1/2-core/ds-tooltip.ts +115 -0
- package/DS1/2-core/generated/ds-icon-map.ts +583 -0
- package/DS1/2-core/generated/icons-extra.ts +243 -0
- package/DS1/2-core/generated/icons-immediate.ts +49 -0
- package/DS1/2-core/styles/ds-avatar.css +125 -0
- package/DS1/2-core/styles/ds-badge.css +26 -0
- package/DS1/2-core/styles/ds-banner.css +77 -0
- package/DS1/2-core/styles/ds-button-host.css +214 -0
- package/DS1/2-core/styles/ds-button.css +131 -0
- package/DS1/2-core/styles/ds-checkbox.css +48 -0
- package/DS1/2-core/styles/ds-color.css +24 -0
- package/DS1/2-core/styles/ds-composer-frame.css +11 -0
- package/DS1/2-core/styles/ds-composer.css +55 -0
- package/DS1/2-core/styles/ds-cycle.css +18 -0
- package/DS1/2-core/styles/ds-date.css +9 -0
- package/DS1/2-core/styles/ds-file-picker.css +66 -0
- package/DS1/2-core/styles/ds-gap.css +71 -0
- package/DS1/2-core/styles/ds-icon.css +44 -0
- package/DS1/2-core/styles/ds-input.css +233 -0
- package/DS1/2-core/styles/ds-language.css +78 -0
- package/DS1/2-core/styles/ds-pagedots.css +31 -0
- package/DS1/2-core/styles/ds-radio.css +83 -0
- package/DS1/2-core/styles/ds-select.css +95 -0
- package/DS1/2-core/styles/ds-split.css +62 -0
- package/DS1/2-core/styles/ds-switch.css +50 -0
- package/DS1/2-core/styles/ds-text.css +87 -0
- package/DS1/2-core/styles/ds-textarea.css +27 -0
- package/DS1/2-core/styles/ds-token.css +9 -0
- package/DS1/2-core/styles/ds-tooltip.css +37 -0
- package/DS1/3-unit/context-trigger.ts +104 -0
- package/DS1/3-unit/ds-accordion.ts +120 -0
- package/DS1/3-unit/ds-avatar-group.ts +99 -0
- package/DS1/3-unit/ds-form.ts +304 -0
- package/DS1/3-unit/ds-list.ts +36 -0
- package/DS1/3-unit/ds-menu.ts +482 -0
- package/DS1/3-unit/ds-row.ts +48 -0
- package/DS1/3-unit/ds-table.ts +68 -0
- package/DS1/3-unit/ds-tabs.ts +77 -0
- package/DS1/3-unit/ds-token-field.ts +127 -0
- package/DS1/3-unit/styles/context-trigger.css +6 -0
- package/DS1/3-unit/styles/ds-accordion.css +64 -0
- package/DS1/3-unit/styles/ds-avatar-group.css +51 -0
- package/DS1/3-unit/styles/ds-list.css +26 -0
- package/DS1/3-unit/styles/ds-menu.css +97 -0
- package/DS1/3-unit/styles/ds-row.css +37 -0
- package/DS1/3-unit/styles/ds-table.css +80 -0
- package/DS1/3-unit/styles/ds-tabs.css +44 -0
- package/DS1/3-unit/styles/ds-token-field.css +11 -0
- package/DS1/4-page/ds-container.ts +28 -0
- package/DS1/4-page/ds-dialog.ts +331 -0
- package/DS1/4-page/ds-grid.ts +74 -0
- package/DS1/4-page/ds-layout.ts +949 -0
- package/DS1/4-page/ds-panel.ts +38 -0
- package/DS1/4-page/email.ts +170 -0
- package/DS1/4-page/styles/ds-container.css +35 -0
- package/DS1/4-page/styles/ds-dialog.css +94 -0
- package/DS1/4-page/styles/ds-grid.css +95 -0
- package/DS1/4-page/styles/ds-layout.css +246 -0
- package/DS1/4-page/styles/ds-panel.css +31 -0
- package/DS1/components.ts +13 -0
- package/DS1/index.ts +86 -0
- package/DS1/react/accordion.ts +33 -0
- package/DS1/react/avatar-group.ts +11 -0
- package/DS1/react/avatar.ts +42 -0
- package/DS1/react/badge.ts +7 -0
- package/DS1/react/button.ts +63 -0
- package/DS1/react/choice.ts +64 -0
- package/DS1/react/composer.ts +18 -0
- package/DS1/react/context-menu.ts +167 -0
- package/DS1/react/dialog.ts +102 -0
- package/DS1/react/file-picker.ts +34 -0
- package/DS1/react/input.ts +184 -0
- package/DS1/react/menu.ts +28 -0
- package/DS1/react/split.ts +42 -0
- package/DS1/react/token-field.ts +32 -0
- package/DS1/vite-env.d.ts +13 -0
- package/DS1/x-icon/1x.svg +4 -0
- package/DS1/x-icon/1xdots.svg +11 -0
- package/DS1/x-icon/1xgrid.svg +8 -0
- package/DS1/x-icon/1xlines.svg +5 -0
- package/DS1/x-icon/2x.svg +4 -0
- package/DS1/x-icon/2xdots.svg +18 -0
- package/DS1/x-icon/2xgrid-alt-2.svg +9 -0
- package/DS1/x-icon/2xgrid-alt-3.svg +8 -0
- package/DS1/x-icon/2xgrid-alt.svg +15 -0
- package/DS1/x-icon/2xgrid.svg +6 -0
- package/DS1/x-icon/2xlines.svg +6 -0
- package/DS1/x-icon/4x4.svg +18 -0
- package/DS1/x-icon/app.svg +4 -0
- package/DS1/x-icon/apple.svg +3 -0
- package/DS1/x-icon/archive-alt.svg +4 -0
- package/DS1/x-icon/archive.svg +4 -0
- package/DS1/x-icon/arrange-archive.svg +1 -0
- package/DS1/x-icon/arrange-check.svg +1 -0
- package/DS1/x-icon/arrange-chevron.svg +1 -0
- package/DS1/x-icon/arrange-connect.svg +1 -0
- package/DS1/x-icon/arrange-draft.svg +1 -0
- package/DS1/x-icon/arrange-dropdown.svg +1 -0
- package/DS1/x-icon/arrange-home.svg +1 -0
- package/DS1/x-icon/arrange-link.svg +1 -0
- package/DS1/x-icon/arrange-menu.svg +1 -0
- package/DS1/x-icon/arrange-more.svg +1 -0
- package/DS1/x-icon/arrange-page.svg +1 -0
- package/DS1/x-icon/arrange-plus.svg +1 -0
- package/DS1/x-icon/arrange-site.svg +1 -0
- package/DS1/x-icon/avatar.svg +4 -0
- package/DS1/x-icon/badge-bands-3d.svg +1 -0
- package/DS1/x-icon/badge-bands.svg +1 -0
- package/DS1/x-icon/badge-blue-fan-3d.svg +1 -0
- package/DS1/x-icon/badge-blue-fan.svg +1 -0
- package/DS1/x-icon/badge-bricks-3d.svg +1 -0
- package/DS1/x-icon/badge-bricks.svg +1 -0
- package/DS1/x-icon/badge-broken-stripes-3d.svg +1 -0
- package/DS1/x-icon/badge-broken-stripes.svg +1 -0
- package/DS1/x-icon/badge-checker-3d.svg +1 -0
- package/DS1/x-icon/badge-checker.svg +1 -0
- package/DS1/x-icon/badge-diamond-3d.svg +1 -0
- package/DS1/x-icon/badge-diamond.svg +1 -0
- package/DS1/x-icon/badge-fan-3d.svg +1 -0
- package/DS1/x-icon/badge-fan-dark-3d.svg +1 -0
- package/DS1/x-icon/badge-fan-dark.svg +1 -0
- package/DS1/x-icon/badge-fan.svg +1 -0
- package/DS1/x-icon/badge-halves-3d.svg +1 -0
- package/DS1/x-icon/badge-halves-green-3d.svg +1 -0
- package/DS1/x-icon/badge-halves-green.svg +1 -0
- package/DS1/x-icon/badge-halves.svg +1 -0
- package/DS1/x-icon/badge-red-3d.svg +1 -0
- package/DS1/x-icon/badge-red-blue-bricks-3d.svg +1 -0
- package/DS1/x-icon/badge-red-blue-bricks.svg +1 -0
- package/DS1/x-icon/badge-red.svg +1 -0
- package/DS1/x-icon/badge-round-fan-3d.svg +1 -0
- package/DS1/x-icon/badge-round-fan.svg +1 -0
- package/DS1/x-icon/badge-shard-3d.svg +1 -0
- package/DS1/x-icon/badge-shard.svg +1 -0
- package/DS1/x-icon/badge-split-circle-3d.svg +1 -0
- package/DS1/x-icon/badge-split-circle.svg +1 -0
- package/DS1/x-icon/badge-tall-bricks-3d.svg +1 -0
- package/DS1/x-icon/badge-tall-bricks.svg +1 -0
- package/DS1/x-icon/badge-weave-3d.svg +1 -0
- package/DS1/x-icon/badge-weave.svg +1 -0
- package/DS1/x-icon/bag.svg +4 -0
- package/DS1/x-icon/block-accordion.svg +1 -0
- package/DS1/x-icon/block-banner.svg +1 -0
- package/DS1/x-icon/block-breadcrumbs.svg +1 -0
- package/DS1/x-icon/block-button.svg +1 -0
- package/DS1/x-icon/block-divider.svg +1 -0
- package/DS1/x-icon/block-forms.svg +1 -0
- package/DS1/x-icon/block-group.svg +1 -0
- package/DS1/x-icon/block-heading.svg +1 -0
- package/DS1/x-icon/block-image.svg +1 -0
- package/DS1/x-icon/block-logo-marquee.svg +1 -0
- package/DS1/x-icon/block-page-progress.svg +1 -0
- package/DS1/x-icon/block-section.svg +1 -0
- package/DS1/x-icon/block-table.svg +1 -0
- package/DS1/x-icon/block-tabs.svg +1 -0
- package/DS1/x-icon/block-text.svg +1 -0
- package/DS1/x-icon/bold.svg +3 -0
- package/DS1/x-icon/bullet.svg +5 -0
- package/DS1/x-icon/calendar.svg +6 -0
- package/DS1/x-icon/callout.svg +5 -0
- package/DS1/x-icon/camera.svg +5 -0
- package/DS1/x-icon/categories.svg +12 -0
- package/DS1/x-icon/check-alt.svg +3 -0
- package/DS1/x-icon/check.svg +3 -0
- package/DS1/x-icon/close.svg +3 -0
- package/DS1/x-icon/code-block.svg +4 -0
- package/DS1/x-icon/code.svg +3 -0
- package/DS1/x-icon/collapse.svg +3 -0
- package/DS1/x-icon/color.svg +4 -0
- package/DS1/x-icon/column.svg +3 -0
- package/DS1/x-icon/comment.svg +3 -0
- package/DS1/x-icon/compose.svg +4 -0
- package/DS1/x-icon/connect.svg +5 -0
- package/DS1/x-icon/content.svg +6 -0
- package/DS1/x-icon/default.svg +4 -0
- package/DS1/x-icon/delete.svg +5 -0
- package/DS1/x-icon/dictate.svg +6 -0
- package/DS1/x-icon/divider.svg +3 -0
- package/DS1/x-icon/down.svg +3 -0
- package/DS1/x-icon/duplicate.svg +8 -0
- package/DS1/x-icon/email.svg +3 -0
- package/DS1/x-icon/equation.svg +3 -0
- package/DS1/x-icon/exercise.svg +3 -0
- package/DS1/x-icon/expand.svg +3 -0
- package/DS1/x-icon/ezo-accordion-chevron.svg +11 -0
- package/DS1/x-icon/ezo-account-close.svg +1 -0
- package/DS1/x-icon/ezo-android-screen-archive.svg +4 -0
- package/DS1/x-icon/ezo-android-screen-calendar.svg +6 -0
- package/DS1/x-icon/ezo-android-screen-camera.svg +5 -0
- package/DS1/x-icon/ezo-android-screen-check.svg +3 -0
- package/DS1/x-icon/ezo-android-screen-compose.svg +4 -0
- package/DS1/x-icon/ezo-android-screen-globe.svg +1 -0
- package/DS1/x-icon/ezo-android-screen-lists.svg +3 -0
- package/DS1/x-icon/ezo-android-screen-shared.svg +5 -0
- package/DS1/x-icon/ezo-android-screen-stats.svg +3 -0
- package/DS1/x-icon/ezo-android-screen-store.svg +4 -0
- package/DS1/x-icon/ezo-android-screen-views.svg +4 -0
- package/DS1/x-icon/ezo-android-screen-work.svg +3 -0
- package/DS1/x-icon/ezo-arrow-cursor.svg +8 -0
- package/DS1/x-icon/ezo-block-accordion.svg +1 -0
- package/DS1/x-icon/ezo-block-divider.svg +1 -0
- package/DS1/x-icon/ezo-block-group.svg +1 -0
- package/DS1/x-icon/ezo-block-image.svg +1 -0
- package/DS1/x-icon/ezo-block-section.svg +1 -0
- package/DS1/x-icon/ezo-block-table.svg +1 -0
- package/DS1/x-icon/ezo-code.svg +14 -0
- package/DS1/x-icon/ezo-down.svg +3 -0
- package/DS1/x-icon/ezo-figma-premium-star.svg +5 -0
- package/DS1/x-icon/ezo-item-button-complete.svg +4 -0
- package/DS1/x-icon/ezo-item-button-header.svg +3 -0
- package/DS1/x-icon/ezo-item-button-open.svg +3 -0
- package/DS1/x-icon/ezo-item-button-reopen.svg +3 -0
- package/DS1/x-icon/ezo-item-button-star.svg +3 -0
- package/DS1/x-icon/ezo-item-button-start.svg +3 -0
- package/DS1/x-icon/ezo-item-button-unheader.svg +7 -0
- package/DS1/x-icon/ezo-item-button-unstar.svg +5 -0
- package/DS1/x-icon/ezo-item-star.svg +5 -0
- package/DS1/x-icon/ezo-left.svg +3 -0
- package/DS1/x-icon/ezo-logo.svg +1 -0
- package/DS1/x-icon/ezo-mic.svg +8 -0
- package/DS1/x-icon/ezo-move-column.svg +14 -0
- package/DS1/x-icon/ezo-move-row.svg +18 -0
- package/DS1/x-icon/ezo-navigation-check.svg +1 -0
- package/DS1/x-icon/ezo-note-back.svg +5 -0
- package/DS1/x-icon/ezo-note-menu-complete.svg +11 -0
- package/DS1/x-icon/ezo-note-menu-delete.svg +4 -0
- package/DS1/x-icon/ezo-note-menu-duplicate.svg +8 -0
- package/DS1/x-icon/ezo-note-menu-note.svg +5 -0
- package/DS1/x-icon/ezo-note-menu-page.svg +3 -0
- package/DS1/x-icon/ezo-note-menu-star.svg +5 -0
- package/DS1/x-icon/ezo-note-menu-start.svg +6 -0
- package/DS1/x-icon/ezo-note-menu-stop.svg +6 -0
- package/DS1/x-icon/ezo-note-more.svg +7 -0
- package/DS1/x-icon/ezo-note-star.svg +5 -0
- package/DS1/x-icon/ezo-password-visibility-see.svg +6 -0
- package/DS1/x-icon/ezo-password-visibility-unsee.svg +9 -0
- package/DS1/x-icon/ezo-pattern-call-to-action.svg +1 -0
- package/DS1/x-icon/ezo-play.svg +5 -0
- package/DS1/x-icon/ezo-premium-star.svg +5 -0
- package/DS1/x-icon/ezo-setting-1xdots.svg +13 -0
- package/DS1/x-icon/ezo-setting-1xgrid.svg +10 -0
- package/DS1/x-icon/ezo-setting-1xlines.svg +7 -0
- package/DS1/x-icon/ezo-setting-2xdots.svg +8 -0
- package/DS1/x-icon/ezo-setting-2xgrid.svg +8 -0
- package/DS1/x-icon/ezo-setting-2xlines.svg +6 -0
- package/DS1/x-icon/ezo-setting-blank.svg +5 -0
- package/DS1/x-icon/ezo-store-chevron.svg +1 -0
- package/DS1/x-icon/ezo-upgrade-close.svg +5 -0
- package/DS1/x-icon/folder.svg +3 -0
- package/DS1/x-icon/form.svg +5 -0
- package/DS1/x-icon/gallery.svg +3 -0
- package/DS1/x-icon/globe.svg +1 -0
- package/DS1/x-icon/google.svg +6 -0
- package/DS1/x-icon/group-alt-2.svg +3 -0
- package/DS1/x-icon/group-alt.svg +7 -0
- package/DS1/x-icon/group.svg +3 -0
- package/DS1/x-icon/h1.svg +3 -0
- package/DS1/x-icon/h2.svg +3 -0
- package/DS1/x-icon/h3.svg +3 -0
- package/DS1/x-icon/handle.svg +8 -0
- package/DS1/x-icon/head.svg +3 -0
- package/DS1/x-icon/help-alt.svg +3 -0
- package/DS1/x-icon/help.svg +5 -0
- package/DS1/x-icon/home.svg +3 -0
- package/DS1/x-icon/icon.svg +4 -0
- package/DS1/x-icon/image.svg +5 -0
- package/DS1/x-icon/in.svg +4 -0
- package/DS1/x-icon/indent.svg +4 -0
- package/DS1/x-icon/italic.svg +3 -0
- package/DS1/x-icon/item.svg +3 -0
- package/DS1/x-icon/left.svg +4 -0
- package/DS1/x-icon/life.svg +8 -0
- package/DS1/x-icon/link.svg +3 -0
- package/DS1/x-icon/list-line.svg +3 -0
- package/DS1/x-icon/lists.svg +3 -0
- package/DS1/x-icon/lock-filled.svg +3 -0
- package/DS1/x-icon/lock.svg +3 -0
- package/DS1/x-icon/logo-marquee.svg +5 -0
- package/DS1/x-icon/loop.svg +6 -0
- package/DS1/x-icon/mention.svg +4 -0
- package/DS1/x-icon/menu.svg +3 -0
- package/DS1/x-icon/mic.svg +5 -0
- package/DS1/x-icon/minimize-alt.svg +4 -0
- package/DS1/x-icon/minimize.svg +4 -0
- package/DS1/x-icon/money.svg +6 -0
- package/DS1/x-icon/more.svg +5 -0
- package/DS1/x-icon/navigation-check.svg +1 -0
- package/DS1/x-icon/note-alt-2.svg +6 -0
- package/DS1/x-icon/note-alt.svg +4 -0
- package/DS1/x-icon/note-lines.svg +6 -0
- package/DS1/x-icon/note.svg +4 -0
- package/DS1/x-icon/notifications.svg +3 -0
- package/DS1/x-icon/numbered.svg +4 -0
- package/DS1/x-icon/open-alt.svg +3 -0
- package/DS1/x-icon/open-source.svg +3 -0
- package/DS1/x-icon/open.svg +3 -0
- package/DS1/x-icon/outdent.svg +4 -0
- package/DS1/x-icon/pad.svg +8 -0
- package/DS1/x-icon/page-18.svg +3 -0
- package/DS1/x-icon/page-alt.svg +3 -0
- package/DS1/x-icon/page-note.svg +1 -0
- package/DS1/x-icon/page.svg +3 -0
- package/DS1/x-icon/panel.svg +4 -0
- package/DS1/x-icon/pattern-call-to-action.svg +1 -0
- package/DS1/x-icon/pattern-card.svg +1 -0
- package/DS1/x-icon/pattern-feature.svg +1 -0
- package/DS1/x-icon/pattern-hero.svg +1 -0
- package/DS1/x-icon/pen.svg +4 -0
- package/DS1/x-icon/play-alt.svg +3 -0
- package/DS1/x-icon/plus.svg +3 -0
- package/DS1/x-icon/product-sources.json +2557 -0
- package/DS1/x-icon/quote.svg +3 -0
- package/DS1/x-icon/redo.svg +3 -0
- package/DS1/x-icon/relationships.svg +3 -0
- package/DS1/x-icon/rewind-30-alt.svg +4 -0
- package/DS1/x-icon/rewind-30.svg +5 -0
- package/DS1/x-icon/rewind.svg +5 -0
- package/DS1/x-icon/right.svg +3 -0
- package/DS1/x-icon/row.svg +3 -0
- package/DS1/x-icon/school.svg +4 -0
- package/DS1/x-icon/sdown.svg +3 -0
- package/DS1/x-icon/search-30.svg +4 -0
- package/DS1/x-icon/search.svg +3 -0
- package/DS1/x-icon/see-30.svg +4 -0
- package/DS1/x-icon/see.svg +4 -0
- package/DS1/x-icon/settings.svg +8 -0
- package/DS1/x-icon/shared.svg +5 -0
- package/DS1/x-icon/slash.svg +3 -0
- package/DS1/x-icon/star-alt-2.svg +3 -0
- package/DS1/x-icon/star-alt.svg +3 -0
- package/DS1/x-icon/star.svg +3 -0
- package/DS1/x-icon/stats.svg +3 -0
- package/DS1/x-icon/status-alt.svg +11 -0
- package/DS1/x-icon/status-filled.svg +26 -0
- package/DS1/x-icon/status.svg +10 -0
- package/DS1/x-icon/store.svg +4 -0
- package/DS1/x-icon/strike.svg +3 -0
- package/DS1/x-icon/sup.svg +3 -0
- package/DS1/x-icon/table.svg +4 -0
- package/DS1/x-icon/template.svg +4 -0
- package/DS1/x-icon/templates.svg +4 -0
- package/DS1/x-icon/text.svg +3 -0
- package/DS1/x-icon/title.svg +3 -0
- package/DS1/x-icon/titlestyle.svg +11 -0
- package/DS1/x-icon/todo.svg +5 -0
- package/DS1/x-icon/toggle.svg +4 -0
- package/DS1/x-icon/triangle.svg +3 -0
- package/DS1/x-icon/underline.svg +3 -0
- package/DS1/x-icon/undo.svg +3 -0
- package/DS1/x-icon/ungroup.svg +4 -0
- package/DS1/x-icon/unhead.svg +7 -0
- package/DS1/x-icon/unicon.svg +6 -0
- package/DS1/x-icon/unlock.svg +3 -0
- package/DS1/x-icon/unmic.svg +6 -0
- package/DS1/x-icon/unsee.svg +5 -0
- package/DS1/x-icon/unstar.svg +5 -0
- package/DS1/x-icon/untitle.svg +7 -0
- package/DS1/x-icon/up-right.svg +3 -0
- package/DS1/x-icon/up.svg +3 -0
- package/DS1/x-icon/views.svg +4 -0
- package/DS1/x-icon/warning.svg +3 -0
- package/DS1/x-icon/work.svg +3 -0
- package/LICENSE +21 -0
- package/README.md +240 -2
- package/dist/0-face/control.d.ts +24 -0
- package/dist/0-face/control.d.ts.map +1 -0
- package/dist/0-face/control.js +117 -0
- package/dist/0-face/define.d.ts +3 -0
- package/dist/0-face/define.d.ts.map +1 -0
- package/dist/0-face/define.js +7 -0
- package/dist/0-face/device.d.ts +71 -0
- package/dist/0-face/device.d.ts.map +1 -0
- package/dist/0-face/device.js +318 -0
- package/dist/0-face/grid-fit.d.ts +3 -0
- package/dist/0-face/grid-fit.d.ts.map +1 -0
- package/dist/0-face/grid-fit.js +73 -0
- package/dist/0-face/grid.d.ts +3 -0
- package/dist/0-face/grid.d.ts.map +1 -0
- package/dist/0-face/grid.js +144 -0
- package/dist/0-face/i18n.d.ts +138 -0
- package/dist/0-face/i18n.d.ts.map +1 -0
- package/dist/0-face/i18n.js +725 -0
- package/dist/0-face/language-routing.d.ts +47 -0
- package/dist/0-face/language-routing.d.ts.map +1 -0
- package/dist/0-face/language-routing.js +91 -0
- package/dist/0-face/language-validator.d.ts +11 -0
- package/dist/0-face/language-validator.d.ts.map +1 -0
- package/dist/0-face/language-validator.js +31 -0
- package/dist/0-face/languages.d.ts +53 -0
- package/dist/0-face/languages.d.ts.map +1 -0
- package/dist/0-face/languages.js +192 -0
- package/dist/0-face/layout-inspector.d.ts +6 -0
- package/dist/0-face/layout-inspector.d.ts.map +1 -0
- package/dist/0-face/layout-inspector.js +197 -0
- package/dist/0-face/preferences.d.ts +20 -0
- package/dist/0-face/preferences.d.ts.map +1 -0
- package/dist/0-face/preferences.js +19 -0
- package/dist/0-face/pricing.d.ts +15 -0
- package/dist/0-face/pricing.d.ts.map +1 -0
- package/dist/0-face/pricing.js +46 -0
- package/dist/0-face/scaling.d.ts +10 -0
- package/dist/0-face/scaling.d.ts.map +1 -0
- package/dist/0-face/scaling.js +14 -0
- package/dist/0-face/shared-words.d.ts +14 -0
- package/dist/0-face/shared-words.d.ts.map +1 -0
- package/dist/0-face/shared-words.js +32 -0
- package/dist/0-face/theme.d.ts +4 -0
- package/dist/0-face/theme.d.ts.map +1 -0
- package/dist/0-face/theme.js +48 -0
- package/dist/0-face/tooltips.d.ts +8 -0
- package/dist/0-face/tooltips.d.ts.map +1 -0
- package/dist/0-face/tooltips.js +90 -0
- package/dist/0-face/translations/da.json +63 -0
- package/dist/0-face/translations/de.json +63 -0
- package/dist/0-face/translations/en.json +63 -0
- package/dist/0-face/translations/es.json +63 -0
- package/dist/0-face/translations/fi.json +63 -0
- package/dist/0-face/translations/fr.json +63 -0
- package/dist/0-face/translations/is.json +56 -0
- package/dist/0-face/translations/it.json +63 -0
- package/dist/0-face/translations/ja.json +63 -0
- package/dist/0-face/translations/ko.json +63 -0
- package/dist/0-face/translations/nb.json +63 -0
- package/dist/0-face/translations/pt-BR.json +63 -0
- package/dist/0-face/translations/pt.json +63 -0
- package/dist/0-face/translations/sv.json +63 -0
- package/dist/0-face/translations/zh.json +63 -0
- package/dist/0-face/translations-format.d.ts +62 -0
- package/dist/0-face/translations-format.d.ts.map +1 -0
- package/dist/0-face/translations-format.js +159 -0
- package/dist/1-root/palette.d.ts +31 -0
- package/dist/1-root/palette.d.ts.map +1 -0
- package/dist/1-root/palette.js +83 -0
- package/dist/2-core/ds-avatar.d.ts +82 -0
- package/dist/2-core/ds-avatar.d.ts.map +1 -0
- package/dist/2-core/ds-avatar.js +102 -0
- package/dist/2-core/ds-badge.d.ts +33 -0
- package/dist/2-core/ds-badge.d.ts.map +1 -0
- package/dist/2-core/ds-badge.js +77 -0
- package/dist/2-core/ds-banner.d.ts +68 -0
- package/dist/2-core/ds-banner.d.ts.map +1 -0
- package/dist/2-core/ds-banner.js +98 -0
- package/dist/2-core/ds-button.d.ts +102 -0
- package/dist/2-core/ds-button.d.ts.map +1 -0
- package/dist/2-core/ds-button.js +133 -0
- package/dist/2-core/ds-card.d.ts +39 -0
- package/dist/2-core/ds-card.d.ts.map +1 -0
- package/dist/2-core/ds-card.js +119 -0
- package/dist/2-core/ds-checkbox.d.ts +13 -0
- package/dist/2-core/ds-checkbox.d.ts.map +1 -0
- package/dist/2-core/ds-checkbox.js +11 -0
- package/dist/2-core/ds-color.d.ts +15 -0
- package/dist/2-core/ds-color.d.ts.map +1 -0
- package/dist/2-core/ds-color.js +30 -0
- package/dist/2-core/ds-composer.d.ts +69 -0
- package/dist/2-core/ds-composer.d.ts.map +1 -0
- package/dist/2-core/ds-composer.js +183 -0
- package/dist/2-core/ds-cycle.d.ts +85 -0
- package/dist/2-core/ds-cycle.d.ts.map +1 -0
- package/dist/2-core/ds-cycle.js +497 -0
- package/dist/2-core/ds-date.d.ts +16 -0
- package/dist/2-core/ds-date.d.ts.map +1 -0
- package/dist/2-core/ds-date.js +15 -0
- package/dist/2-core/ds-file-picker.d.ts +65 -0
- package/dist/2-core/ds-file-picker.d.ts.map +1 -0
- package/dist/2-core/ds-file-picker.js +80 -0
- package/dist/2-core/ds-gap.d.ts +27 -0
- package/dist/2-core/ds-gap.d.ts.map +1 -0
- package/dist/2-core/ds-gap.js +22 -0
- package/dist/2-core/ds-icon.d.ts +58 -0
- package/dist/2-core/ds-icon.d.ts.map +1 -0
- package/dist/2-core/ds-icon.js +128 -0
- package/dist/2-core/ds-input.d.ts +97 -0
- package/dist/2-core/ds-input.d.ts.map +1 -0
- package/dist/2-core/ds-input.js +429 -0
- package/dist/2-core/ds-language.d.ts +75 -0
- package/dist/2-core/ds-language.d.ts.map +1 -0
- package/dist/2-core/ds-language.js +74 -0
- package/dist/2-core/ds-pagedots.d.ts +32 -0
- package/dist/2-core/ds-pagedots.d.ts.map +1 -0
- package/dist/2-core/ds-pagedots.js +36 -0
- package/dist/2-core/ds-radio.d.ts +35 -0
- package/dist/2-core/ds-radio.d.ts.map +1 -0
- package/dist/2-core/ds-radio.js +19 -0
- package/dist/2-core/ds-select.d.ts +30 -0
- package/dist/2-core/ds-select.d.ts.map +1 -0
- package/dist/2-core/ds-select.js +45 -0
- package/dist/2-core/ds-split.d.ts +75 -0
- package/dist/2-core/ds-split.d.ts.map +1 -0
- package/dist/2-core/ds-split.js +101 -0
- package/dist/2-core/ds-swatch.d.ts +24 -0
- package/dist/2-core/ds-swatch.d.ts.map +1 -0
- package/dist/2-core/ds-swatch.js +16 -0
- package/dist/2-core/ds-switch.d.ts +13 -0
- package/dist/2-core/ds-switch.d.ts.map +1 -0
- package/dist/2-core/ds-switch.js +10 -0
- package/dist/2-core/ds-text.d.ts +75 -0
- package/dist/2-core/ds-text.d.ts.map +1 -0
- package/dist/2-core/ds-text.js +142 -0
- package/dist/2-core/ds-textarea.d.ts +46 -0
- package/dist/2-core/ds-textarea.d.ts.map +1 -0
- package/dist/2-core/ds-textarea.js +121 -0
- package/dist/2-core/ds-token.d.ts +48 -0
- package/dist/2-core/ds-token.d.ts.map +1 -0
- package/dist/2-core/ds-token.js +44 -0
- package/dist/2-core/ds-tooltip.d.ts +48 -0
- package/dist/2-core/ds-tooltip.d.ts.map +1 -0
- package/dist/2-core/ds-tooltip.js +99 -0
- package/dist/2-core/generated/ds-icon-map.d.ts +295 -0
- package/dist/2-core/generated/ds-icon-map.d.ts.map +1 -0
- package/dist/2-core/generated/ds-icon-map.js +579 -0
- package/dist/2-core/generated/icons-extra.d.ts +2 -0
- package/dist/2-core/generated/icons-extra.d.ts.map +1 -0
- package/dist/2-core/generated/icons-extra.js +243 -0
- package/dist/2-core/generated/icons-immediate.d.ts +3 -0
- package/dist/2-core/generated/icons-immediate.d.ts.map +1 -0
- package/dist/2-core/generated/icons-immediate.js +49 -0
- package/dist/3-unit/context-trigger.d.ts +22 -0
- package/dist/3-unit/context-trigger.d.ts.map +1 -0
- package/dist/3-unit/context-trigger.js +130 -0
- package/dist/3-unit/ds-accordion.d.ts +55 -0
- package/dist/3-unit/ds-accordion.d.ts.map +1 -0
- package/dist/3-unit/ds-accordion.js +105 -0
- package/dist/3-unit/ds-avatar-group.d.ts +40 -0
- package/dist/3-unit/ds-avatar-group.d.ts.map +1 -0
- package/dist/3-unit/ds-avatar-group.js +84 -0
- package/dist/3-unit/ds-form.d.ts +70 -0
- package/dist/3-unit/ds-form.d.ts.map +1 -0
- package/dist/3-unit/ds-form.js +232 -0
- package/dist/3-unit/ds-list.d.ts +25 -0
- package/dist/3-unit/ds-list.d.ts.map +1 -0
- package/dist/3-unit/ds-list.js +25 -0
- package/dist/3-unit/ds-menu.d.ts +162 -0
- package/dist/3-unit/ds-menu.d.ts.map +1 -0
- package/dist/3-unit/ds-menu.js +607 -0
- package/dist/3-unit/ds-row.d.ts +43 -0
- package/dist/3-unit/ds-row.d.ts.map +1 -0
- package/dist/3-unit/ds-row.js +32 -0
- package/dist/3-unit/ds-table.d.ts +33 -0
- package/dist/3-unit/ds-table.d.ts.map +1 -0
- package/dist/3-unit/ds-table.js +43 -0
- package/dist/3-unit/ds-tabs.d.ts +50 -0
- package/dist/3-unit/ds-tabs.d.ts.map +1 -0
- package/dist/3-unit/ds-tabs.js +64 -0
- package/dist/3-unit/ds-token-field.d.ts +64 -0
- package/dist/3-unit/ds-token-field.d.ts.map +1 -0
- package/dist/3-unit/ds-token-field.js +184 -0
- package/dist/4-page/ds-container.d.ts +17 -0
- package/dist/4-page/ds-container.d.ts.map +1 -0
- package/dist/4-page/ds-container.js +11 -0
- package/dist/4-page/ds-dialog.d.ts +105 -0
- package/dist/4-page/ds-dialog.d.ts.map +1 -0
- package/dist/4-page/ds-dialog.js +358 -0
- package/dist/4-page/ds-grid.d.ts +28 -0
- package/dist/4-page/ds-grid.d.ts.map +1 -0
- package/dist/4-page/ds-grid.js +49 -0
- package/dist/4-page/ds-layout.d.ts +31 -0
- package/dist/4-page/ds-layout.d.ts.map +1 -0
- package/dist/4-page/ds-layout.js +930 -0
- package/dist/4-page/ds-panel.d.ts +26 -0
- package/dist/4-page/ds-panel.d.ts.map +1 -0
- package/dist/4-page/ds-panel.js +29 -0
- package/dist/4-page/email.d.ts +49 -0
- package/dist/4-page/email.d.ts.map +1 -0
- package/dist/4-page/email.js +134 -0
- package/dist/components.d.ts +14 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +13 -0
- package/dist/design-system-1.bundle.js +9115 -0
- package/dist/design-system-1.bundle.js.map +1 -0
- package/dist/design-system-1.bundle.min.js +6879 -0
- package/dist/design-system-1.bundle.min.js.map +1 -0
- package/dist/design-system-1.css +2 -0
- package/dist/icons-extra-BoAACA4X.js +944 -0
- package/dist/icons-extra-BoAACA4X.js.map +1 -0
- package/dist/icons-extra-DlxTo_Y4.js +247 -0
- package/dist/icons-extra-DlxTo_Y4.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/react/accordion.d.ts +21 -0
- package/dist/react/accordion.d.ts.map +1 -0
- package/dist/react/accordion.js +30 -0
- package/dist/react/avatar-group.d.ts +6 -0
- package/dist/react/avatar-group.d.ts.map +1 -0
- package/dist/react/avatar-group.js +5 -0
- package/dist/react/avatar.d.ts +13 -0
- package/dist/react/avatar.d.ts.map +1 -0
- package/dist/react/avatar.js +18 -0
- package/dist/react/badge.d.ts +11 -0
- package/dist/react/badge.d.ts.map +1 -0
- package/dist/react/badge.js +4 -0
- package/dist/react/button.d.ts +13 -0
- package/dist/react/button.d.ts.map +1 -0
- package/dist/react/button.js +37 -0
- package/dist/react/choice.d.ts +43 -0
- package/dist/react/choice.d.ts.map +1 -0
- package/dist/react/choice.js +32 -0
- package/dist/react/composer.d.ts +17 -0
- package/dist/react/composer.d.ts.map +1 -0
- package/dist/react/composer.js +8 -0
- package/dist/react/context-menu.d.ts +657 -0
- package/dist/react/context-menu.d.ts.map +1 -0
- package/dist/react/context-menu.js +133 -0
- package/dist/react/dialog.d.ts +1173 -0
- package/dist/react/dialog.d.ts.map +1 -0
- package/dist/react/dialog.js +79 -0
- package/dist/react/file-picker.d.ts +29 -0
- package/dist/react/file-picker.d.ts.map +1 -0
- package/dist/react/file-picker.js +17 -0
- package/dist/react/input.d.ts +35 -0
- package/dist/react/input.d.ts.map +1 -0
- package/dist/react/input.js +128 -0
- package/dist/react/menu.d.ts +37 -0
- package/dist/react/menu.d.ts.map +1 -0
- package/dist/react/menu.js +31 -0
- package/dist/react/split.d.ts +15 -0
- package/dist/react/split.d.ts.map +1 -0
- package/dist/react/split.js +32 -0
- package/dist/react/token-field.d.ts +36 -0
- package/dist/react/token-field.d.ts.map +1 -0
- package/dist/react/token-field.js +33 -0
- package/dist/shared-words-Cg6Phtx0.js +915 -0
- package/dist/shared-words-Cg6Phtx0.js.map +1 -0
- package/dist/shared-words-Ddz0yUEF.js +933 -0
- package/dist/shared-words-Ddz0yUEF.js.map +1 -0
- package/package.json +149 -3
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LANGUAGES,
|
|
3
|
+
LANGUAGE_METADATA,
|
|
4
|
+
fallbackLanguages,
|
|
5
|
+
isLanguage,
|
|
6
|
+
languageOrder,
|
|
7
|
+
normalizeLanguage,
|
|
8
|
+
} from "./languages";
|
|
9
|
+
import type { Language } from "./languages";
|
|
10
|
+
import { configureLanguageRouting } from "./language-routing";
|
|
11
|
+
import type { LanguageRoutingOptions } from "./language-routing";
|
|
12
|
+
import { readLanguageFile, resolveText } from "./translations-format";
|
|
13
|
+
import type {
|
|
14
|
+
TranslationParams,
|
|
15
|
+
TranslationTable,
|
|
16
|
+
TranslationsFile,
|
|
17
|
+
} from "./translations-format";
|
|
18
|
+
|
|
19
|
+
// Define the supported languages using ISO codes (extensible)
|
|
20
|
+
export type LanguageCode = string;
|
|
21
|
+
|
|
22
|
+
// One language's words, key to text
|
|
23
|
+
export type TranslationData = {
|
|
24
|
+
[key: string]: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Every language's words, keyed by language code
|
|
28
|
+
export type TranslationMap = Record<string, TranslationData>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How a product sets up the language module. Every field is optional: without
|
|
32
|
+
* any, the module behaves as it always has (the whole table from
|
|
33
|
+
* `./translations.json`, the languages offered are the ones in the words).
|
|
34
|
+
*
|
|
35
|
+
* Pass it to `configureI18n()` straight after importing the module, or put it
|
|
36
|
+
* on `window.DS_ONE_I18N` before the module loads.
|
|
37
|
+
*/
|
|
38
|
+
export type I18nOptions = {
|
|
39
|
+
/** Fetch external words at startup. Disable when the product supplies them in code. */
|
|
40
|
+
autoLoad?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Every language the product speaks. When set, the picker offers these
|
|
43
|
+
* whether or not their words have arrived yet, which matters when a page
|
|
44
|
+
* loads only its own language (`languageFile`). Without it, the languages
|
|
45
|
+
* offered are the ones found in the loaded words.
|
|
46
|
+
*/
|
|
47
|
+
languages?: readonly LanguageCode[];
|
|
48
|
+
/**
|
|
49
|
+
* Where one language's words are published, so a page loads its own
|
|
50
|
+
* language rather than the whole table: a pattern with `{language}` in it
|
|
51
|
+
* (`"/translations/{language}.json"`) or a function. Each file holds
|
|
52
|
+
* `{ "<language>": { …words } }`. When a language's file is missing, the
|
|
53
|
+
* whole table is the fallback. Without it, only the whole table is loaded.
|
|
54
|
+
*/
|
|
55
|
+
languageFile?: string | ((language: LanguageCode) => string) | null;
|
|
56
|
+
/**
|
|
57
|
+
* Where the whole table is published. Defaults to `./translations.json`.
|
|
58
|
+
* `window.DS_ONE_TRANSLATIONS_FILE` and the `data-ds-one-translations`,
|
|
59
|
+
* `ds-one:translations` and `ds-one-translations` markers in the page win
|
|
60
|
+
* over it.
|
|
61
|
+
*/
|
|
62
|
+
translationsFile?: string;
|
|
63
|
+
/** The localStorage key that keeps the chosen language. Defaults to `language`. */
|
|
64
|
+
storageKey?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Report keys with no translation on the console, each once. Off by
|
|
67
|
+
* default, since components also show what people write themselves, and
|
|
68
|
+
* that is not a key anyone forgot. `window.DS_ONE_I18N_DEBUG = true` or
|
|
69
|
+
* localStorage `ds-one:i18n-debug` set to `on` turn it on as well.
|
|
70
|
+
*/
|
|
71
|
+
reportMissingKeys?: boolean | (() => boolean);
|
|
72
|
+
/**
|
|
73
|
+
* Load Design system 1's shared interface words (buttons, sign-in, empty
|
|
74
|
+
* states, dates, language names) beneath the product's own words, so the
|
|
75
|
+
* product's text wins where both have a key. Off by default.
|
|
76
|
+
*/
|
|
77
|
+
sharedWords?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Which languages get an address prefix (`/ja/…`) and whether the default
|
|
80
|
+
* language has one; see `configureLanguageRouting`. Without `languages`
|
|
81
|
+
* here, the product's `languages` get a prefix.
|
|
82
|
+
*/
|
|
83
|
+
routing?: LanguageRoutingOptions;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Bundled translations (keys.json may not exist - will use external translations if not available)
|
|
87
|
+
// This is a fallback for when external translations aren't loaded
|
|
88
|
+
let translationKeys: TranslationMap = {};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Every language Design system 1 orders, names and maps browser languages to:
|
|
92
|
+
* the list in `./languages` (`LANGUAGES`). A product turns on the ones it
|
|
93
|
+
* translates with `configureI18n({ languages })`.
|
|
94
|
+
*/
|
|
95
|
+
export const SUPPORTED_LANGUAGES: readonly Language[] = LANGUAGES;
|
|
96
|
+
|
|
97
|
+
const DISPLAY_NAME_CACHE = new Map<string, Intl.DisplayNames>();
|
|
98
|
+
let displayNameFallbackWarningShown = false;
|
|
99
|
+
|
|
100
|
+
// Declare window property for external translations
|
|
101
|
+
declare global {
|
|
102
|
+
interface Window {
|
|
103
|
+
DS_ONE_TRANSLATIONS?: Record<string, TranslationData>;
|
|
104
|
+
DS_ONE_TRANSLATIONS_FILE?: string;
|
|
105
|
+
DS_ONE_I18N?: I18nOptions;
|
|
106
|
+
DS_ONE_I18N_DEBUG?: boolean;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const DEFAULT_TRANSLATION_FILE = "./translations.json";
|
|
111
|
+
const DEFAULT_STORAGE_KEY = "language";
|
|
112
|
+
const DEBUG_STORAGE_KEY = "ds-one:i18n-debug";
|
|
113
|
+
|
|
114
|
+
const defaultLanguage: LanguageCode = "en";
|
|
115
|
+
|
|
116
|
+
let i18nOptions: I18nOptions =
|
|
117
|
+
typeof window !== "undefined" && window.DS_ONE_I18N
|
|
118
|
+
? { ...window.DS_ONE_I18N }
|
|
119
|
+
: {};
|
|
120
|
+
|
|
121
|
+
// Design system 1's shared words, kept apart from the product's so a product's
|
|
122
|
+
// file arriving never wipes them, and looked up beneath the product's words.
|
|
123
|
+
let sharedWords: TranslationsFile | undefined;
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Storage
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
function storage(): Storage | undefined {
|
|
130
|
+
try {
|
|
131
|
+
return typeof window !== "undefined"
|
|
132
|
+
? (window.localStorage ?? undefined)
|
|
133
|
+
: undefined;
|
|
134
|
+
} catch {
|
|
135
|
+
// Storage can be blocked (private windows, cleared site data)
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function storageKey(): string {
|
|
141
|
+
return i18nOptions.storageKey || DEFAULT_STORAGE_KEY;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function readStoredLanguage(): LanguageCode | null {
|
|
145
|
+
try {
|
|
146
|
+
return (storage()?.getItem(storageKey()) as LanguageCode | null) || null;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function storeLanguage(language: LanguageCode): void {
|
|
153
|
+
try {
|
|
154
|
+
storage()?.setItem(storageKey(), language);
|
|
155
|
+
} catch {
|
|
156
|
+
// The choice still holds for this page
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Language codes
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
function extractPrimarySubtag(code: LanguageCode): string {
|
|
165
|
+
if (!code) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
return code.toLowerCase().split(/[-_]/)[0] ?? "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The language whose file a page loads for `language`: one of the configured
|
|
173
|
+
* languages when a list is set (null when it speaks none of them), otherwise
|
|
174
|
+
* the primary subtag.
|
|
175
|
+
*/
|
|
176
|
+
function fileLanguage(language: LanguageCode): LanguageCode | null {
|
|
177
|
+
const configured = i18nOptions.languages;
|
|
178
|
+
if (!configured || configured.length === 0) {
|
|
179
|
+
return extractPrimarySubtag(language) || null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const normalized = (language || "").toLowerCase().replace("_", "-");
|
|
183
|
+
const exact = configured.find((code) => code.toLowerCase() === normalized);
|
|
184
|
+
if (exact) {
|
|
185
|
+
return exact;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const primary = extractPrimarySubtag(language);
|
|
189
|
+
return configured.find((code) => code.toLowerCase() === primary) ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function languageFileFor(language: LanguageCode): string | null {
|
|
193
|
+
const pattern = i18nOptions.languageFile;
|
|
194
|
+
if (!pattern) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const path =
|
|
198
|
+
typeof pattern === "function"
|
|
199
|
+
? pattern(language)
|
|
200
|
+
: pattern.split("{language}").join(language);
|
|
201
|
+
return normalizeCandidate(path);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Loading translations
|
|
206
|
+
// ============================================================================
|
|
207
|
+
|
|
208
|
+
// CDN Loader: Automatically detects and loads translation JSON files
|
|
209
|
+
// for CDN users who want to use external translations
|
|
210
|
+
|
|
211
|
+
let loadAttempted = false;
|
|
212
|
+
|
|
213
|
+
function normalizeCandidate(path: string): string | null {
|
|
214
|
+
if (!path) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const trimmed = path.trim();
|
|
218
|
+
if (!trimmed) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
trimmed.startsWith("./") ||
|
|
224
|
+
trimmed.startsWith("../") ||
|
|
225
|
+
trimmed.startsWith("/") ||
|
|
226
|
+
/^https?:\/\//i.test(trimmed)
|
|
227
|
+
) {
|
|
228
|
+
return trimmed;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return `./${trimmed}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function findAttributeCandidate(): string | null {
|
|
235
|
+
if (typeof document === "undefined" || !document.querySelector) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const scriptWithAttribute = document.querySelector(
|
|
240
|
+
"script[data-ds-one-translations]"
|
|
241
|
+
);
|
|
242
|
+
const scriptCandidate = scriptWithAttribute?.getAttribute(
|
|
243
|
+
"data-ds-one-translations"
|
|
244
|
+
);
|
|
245
|
+
if (scriptCandidate) {
|
|
246
|
+
return scriptCandidate;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const metaCandidate = document
|
|
250
|
+
.querySelector('meta[name="ds-one:translations"]')
|
|
251
|
+
?.getAttribute("content");
|
|
252
|
+
if (metaCandidate) {
|
|
253
|
+
return metaCandidate;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const linkCandidate = document
|
|
257
|
+
.querySelector('link[rel="ds-one-translations"]')
|
|
258
|
+
?.getAttribute("href");
|
|
259
|
+
if (linkCandidate) {
|
|
260
|
+
return linkCandidate;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Where the whole table is: what the page names, else the option, else the default. */
|
|
267
|
+
function resolveTableSources(): string[] {
|
|
268
|
+
const candidates: string[] = [];
|
|
269
|
+
|
|
270
|
+
const windowCandidate =
|
|
271
|
+
typeof window !== "undefined" ? window.DS_ONE_TRANSLATIONS_FILE : null;
|
|
272
|
+
const attributeCandidate = findAttributeCandidate();
|
|
273
|
+
|
|
274
|
+
// Only use explicitly configured paths, or the single default
|
|
275
|
+
const windowNormalized = normalizeCandidate(windowCandidate ?? "");
|
|
276
|
+
if (windowNormalized) {
|
|
277
|
+
candidates.push(windowNormalized);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const attrNormalized = normalizeCandidate(attributeCandidate ?? "");
|
|
281
|
+
if (attrNormalized && !candidates.includes(attrNormalized)) {
|
|
282
|
+
candidates.push(attrNormalized);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Only try the default if no explicit path was configured
|
|
286
|
+
if (candidates.length === 0) {
|
|
287
|
+
candidates.push(
|
|
288
|
+
normalizeCandidate(i18nOptions.translationsFile ?? "") ??
|
|
289
|
+
DEFAULT_TRANSLATION_FILE
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return candidates;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function resolveTranslationSources(): string[] {
|
|
297
|
+
const tables = resolveTableSources();
|
|
298
|
+
// With one file per language, the page's own language first, then the
|
|
299
|
+
// whole table.
|
|
300
|
+
const startLanguage =
|
|
301
|
+
fileLanguage(currentLanguage.value) ?? defaultLanguage;
|
|
302
|
+
const own = languageFileFor(startLanguage);
|
|
303
|
+
return own ? [own, ...tables.filter((source) => source !== own)] : tables;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** The language whose own file `source` is, when it is one. */
|
|
307
|
+
function languageOfSource(source: string): LanguageCode | undefined {
|
|
308
|
+
const start = fileLanguage(currentLanguage.value) ?? defaultLanguage;
|
|
309
|
+
return languageFileFor(start) === source ? start : undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function validateTranslationMap(
|
|
313
|
+
candidate: unknown
|
|
314
|
+
): candidate is TranslationMap {
|
|
315
|
+
if (!candidate || typeof candidate !== "object") {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return Object.values(candidate).every(
|
|
320
|
+
(entry) => entry && typeof entry === "object"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// One request per file, shared by startup and a language switch alike. A file
|
|
325
|
+
// that failed is forgotten, so a later attempt asks again.
|
|
326
|
+
const translationFiles = new Map<string, Promise<TranslationMap | null>>();
|
|
327
|
+
|
|
328
|
+
function fetchTranslationFile(
|
|
329
|
+
source: string,
|
|
330
|
+
language?: LanguageCode
|
|
331
|
+
): Promise<TranslationMap | null> {
|
|
332
|
+
let pending = translationFiles.get(source);
|
|
333
|
+
if (!pending) {
|
|
334
|
+
pending = readTranslationFile(source, language).then((translations) => {
|
|
335
|
+
if (!translations) {
|
|
336
|
+
translationFiles.delete(source);
|
|
337
|
+
}
|
|
338
|
+
return translations;
|
|
339
|
+
});
|
|
340
|
+
translationFiles.set(source, pending);
|
|
341
|
+
}
|
|
342
|
+
return pending;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function readTranslationFile(
|
|
346
|
+
source: string,
|
|
347
|
+
language?: LanguageCode
|
|
348
|
+
): Promise<TranslationMap | null> {
|
|
349
|
+
try {
|
|
350
|
+
const response = await fetch(source);
|
|
351
|
+
|
|
352
|
+
if (!response.ok) {
|
|
353
|
+
// 404 is expected if no translations file exists - don't log as error
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const json: unknown = await response.json();
|
|
358
|
+
// A language's own file may hold its table bare ({ "Save": "Gem" })
|
|
359
|
+
const translations = language
|
|
360
|
+
? (readLanguageFile(language, json) ?? json)
|
|
361
|
+
: json;
|
|
362
|
+
|
|
363
|
+
if (!validateTranslationMap(translations)) {
|
|
364
|
+
console.warn(
|
|
365
|
+
`[Design system 1] Invalid translation format in ${source}. Expected object with language codes as keys.`
|
|
366
|
+
);
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const languages = Object.keys(translations);
|
|
371
|
+
if (languages.length === 0) {
|
|
372
|
+
console.warn(`[Design system 1] No languages found in ${source}`);
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return translations;
|
|
377
|
+
} catch {
|
|
378
|
+
// Silently fail - file likely doesn't exist or isn't valid JSON
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Add words to what is loaded, keeping the languages already there. */
|
|
384
|
+
function mergeTranslations(translations: TranslationMap): void {
|
|
385
|
+
if (typeof window === "undefined") {
|
|
386
|
+
translationKeys = { ...translationKeys, ...translations };
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
window.DS_ONE_TRANSLATIONS = {
|
|
390
|
+
...window.DS_ONE_TRANSLATIONS,
|
|
391
|
+
...translations,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Attempts to load translations from a JSON file in the same directory
|
|
397
|
+
*/
|
|
398
|
+
async function loadExternalTranslations(): Promise<boolean> {
|
|
399
|
+
// Only attempt once
|
|
400
|
+
if (loadAttempted) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
loadAttempted = true;
|
|
404
|
+
|
|
405
|
+
if (typeof window === "undefined") {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Check if translations are already loaded (e.g., by the application)
|
|
410
|
+
if (
|
|
411
|
+
window.DS_ONE_TRANSLATIONS &&
|
|
412
|
+
Object.keys(window.DS_ONE_TRANSLATIONS).length > 0
|
|
413
|
+
) {
|
|
414
|
+
debugLog(
|
|
415
|
+
`Translations already loaded (${Object.keys(window.DS_ONE_TRANSLATIONS).length} languages), skipping auto-load`
|
|
416
|
+
);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const sources = resolveTranslationSources();
|
|
421
|
+
|
|
422
|
+
for (const source of sources) {
|
|
423
|
+
const translations = await fetchTranslationFile(
|
|
424
|
+
source,
|
|
425
|
+
languageOfSource(source)
|
|
426
|
+
);
|
|
427
|
+
if (!translations) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
mergeTranslations(translations);
|
|
432
|
+
|
|
433
|
+
const languages = Object.keys(translations);
|
|
434
|
+
debugLog(
|
|
435
|
+
`External translations loaded from ${source}: ${languages.length} language(s) – ${languages.join(", ")}`
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
window.dispatchEvent(new CustomEvent("translations-ready"));
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
debugLog(
|
|
443
|
+
`No external translations found at ${sources[0] ?? DEFAULT_TRANSLATION_FILE}. Using bundled translations.`
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const languageLoads = new Map<string, Promise<void>>();
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Fetch a language's words when the page needs one it has not got: its own
|
|
453
|
+
* file, or the whole table when that file is missing. Does nothing unless
|
|
454
|
+
* `languageFile` is configured, since otherwise the whole table, with every
|
|
455
|
+
* language in it, is what the page loads at startup.
|
|
456
|
+
*/
|
|
457
|
+
export function loadLanguage(language: LanguageCode): Promise<void> {
|
|
458
|
+
if (typeof window === "undefined" || !i18nOptions.languageFile) {
|
|
459
|
+
return Promise.resolve();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const code = fileLanguage(language) ?? defaultLanguage;
|
|
463
|
+
if (getTranslationData()[code]) {
|
|
464
|
+
return Promise.resolve();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let pending = languageLoads.get(code);
|
|
468
|
+
if (!pending) {
|
|
469
|
+
pending = (async () => {
|
|
470
|
+
const own = languageFileFor(code);
|
|
471
|
+
let translations = own ? await fetchTranslationFile(own, code) : null;
|
|
472
|
+
for (const source of resolveTableSources()) {
|
|
473
|
+
if (translations) {
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
translations = await fetchTranslationFile(source);
|
|
477
|
+
}
|
|
478
|
+
if (!translations) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
mergeTranslations(translations);
|
|
482
|
+
window.dispatchEvent(new CustomEvent("translations-ready"));
|
|
483
|
+
})();
|
|
484
|
+
languageLoads.set(code, pending);
|
|
485
|
+
}
|
|
486
|
+
return pending;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Get translation data - prioritize external, fall back to bundled
|
|
490
|
+
function getTranslationData(): TranslationMap {
|
|
491
|
+
// Check for externally loaded translations first (CDN usage)
|
|
492
|
+
if (typeof window !== "undefined" && window.DS_ONE_TRANSLATIONS) {
|
|
493
|
+
return window.DS_ONE_TRANSLATIONS;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Fall back to bundled translations
|
|
497
|
+
return translationKeys;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// Language names and ordering
|
|
502
|
+
// ============================================================================
|
|
503
|
+
|
|
504
|
+
function sortLanguageCodes(codes: LanguageCode[]): LanguageCode[] {
|
|
505
|
+
return [...codes].sort((a, b) => {
|
|
506
|
+
const priorityA = languageOrder(a);
|
|
507
|
+
const priorityB = languageOrder(b);
|
|
508
|
+
|
|
509
|
+
if (priorityA !== priorityB) {
|
|
510
|
+
return priorityA - priorityB;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return a.localeCompare(b);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function getDisplayNameForLocale(
|
|
518
|
+
locale: string,
|
|
519
|
+
code: LanguageCode
|
|
520
|
+
): string | undefined {
|
|
521
|
+
const normalizedLocale = locale?.replace("_", "-");
|
|
522
|
+
if (!normalizedLocale) {
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
let displayNames = DISPLAY_NAME_CACHE.get(normalizedLocale);
|
|
528
|
+
|
|
529
|
+
if (!displayNames) {
|
|
530
|
+
displayNames = new Intl.DisplayNames([normalizedLocale], {
|
|
531
|
+
type: "language",
|
|
532
|
+
});
|
|
533
|
+
DISPLAY_NAME_CACHE.set(normalizedLocale, displayNames);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const cleanedCode = code.replace("_", "-");
|
|
537
|
+
|
|
538
|
+
// Try full tag first
|
|
539
|
+
const fullMatch = displayNames.of(cleanedCode);
|
|
540
|
+
if (fullMatch && fullMatch !== cleanedCode) {
|
|
541
|
+
return fullMatch;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Then the primary subtag
|
|
545
|
+
const baseMatch = displayNames.of(extractPrimarySubtag(cleanedCode));
|
|
546
|
+
if (baseMatch) {
|
|
547
|
+
return baseMatch;
|
|
548
|
+
}
|
|
549
|
+
} catch {
|
|
550
|
+
// Intl.DisplayNames may not be supported; fall back gracefully
|
|
551
|
+
if (!displayNameFallbackWarningShown) {
|
|
552
|
+
console.info(
|
|
553
|
+
"[Design system 1] Intl.DisplayNames is not available, using fallback language names."
|
|
554
|
+
);
|
|
555
|
+
displayNameFallbackWarningShown = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// English names from the language list, for when Intl.DisplayNames is missing
|
|
563
|
+
function getFallbackDisplayName(code: LanguageCode): string | undefined {
|
|
564
|
+
const language = normalizeLanguage(code, null);
|
|
565
|
+
return language ? LANGUAGE_METADATA[language].label : undefined;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function getLanguageDisplayName(
|
|
569
|
+
code: LanguageCode,
|
|
570
|
+
options: { locale?: string } = {}
|
|
571
|
+
): string {
|
|
572
|
+
if (!code) {
|
|
573
|
+
return "";
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const localesToTry: string[] = [];
|
|
577
|
+
|
|
578
|
+
if (options.locale) {
|
|
579
|
+
localesToTry.push(options.locale);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (typeof navigator !== "undefined") {
|
|
583
|
+
if (Array.isArray(navigator.languages)) {
|
|
584
|
+
localesToTry.push(...navigator.languages);
|
|
585
|
+
}
|
|
586
|
+
if (navigator.language) {
|
|
587
|
+
localesToTry.push(navigator.language);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
localesToTry.push(defaultLanguage);
|
|
592
|
+
localesToTry.push("en");
|
|
593
|
+
|
|
594
|
+
const tried = new Set<string>();
|
|
595
|
+
|
|
596
|
+
for (const locale of localesToTry) {
|
|
597
|
+
if (!locale || tried.has(locale)) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
tried.add(locale);
|
|
601
|
+
|
|
602
|
+
const displayName = getDisplayNameForLocale(locale, code);
|
|
603
|
+
if (displayName) {
|
|
604
|
+
return displayName;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const fallback = getFallbackDisplayName(code);
|
|
609
|
+
if (fallback) {
|
|
610
|
+
return fallback;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Fall back to the primary subtag in uppercase
|
|
614
|
+
const primary = extractPrimarySubtag(code);
|
|
615
|
+
return primary ? primary.toUpperCase() : code;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ============================================================================
|
|
619
|
+
// Browser language
|
|
620
|
+
// ============================================================================
|
|
621
|
+
|
|
622
|
+
/** The languages a browser's language may become: the product's, else the list. */
|
|
623
|
+
function configuredLanguages(): readonly LanguageCode[] | null {
|
|
624
|
+
return i18nOptions.languages?.length ? i18nOptions.languages : null;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* A browser language tag as one the product speaks: within the configured
|
|
629
|
+
* languages when they are set (a relative standing in, so `no` reads `nb` for
|
|
630
|
+
* a product that speaks `nb`), else within the whole list. Null when none fits.
|
|
631
|
+
*/
|
|
632
|
+
function resolvePreferredLanguage(languageTag: string): LanguageCode | null {
|
|
633
|
+
if (!languageTag) {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const configured = configuredLanguages();
|
|
638
|
+
const members = configured
|
|
639
|
+
? configured.filter(isLanguage)
|
|
640
|
+
: (LANGUAGES as readonly Language[]);
|
|
641
|
+
|
|
642
|
+
const match = members.length
|
|
643
|
+
? normalizeLanguage(languageTag, null, members)
|
|
644
|
+
: null;
|
|
645
|
+
if (match) {
|
|
646
|
+
return match;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// A configured language outside the list (`nl`), by its primary subtag
|
|
650
|
+
return configured ? fileLanguage(languageTag) : null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* The browser's language, as one the product speaks: the first of the
|
|
655
|
+
* browser's languages that fits. When none does, a product that configured
|
|
656
|
+
* its languages gets English; one that configured nothing gets the browser's
|
|
657
|
+
* own tag, since its words may still be loaded under it.
|
|
658
|
+
*/
|
|
659
|
+
export function getBrowserLanguage(): LanguageCode {
|
|
660
|
+
if (typeof navigator === "undefined" || !navigator) {
|
|
661
|
+
return defaultLanguage;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const candidates = [
|
|
665
|
+
navigator.language,
|
|
666
|
+
...(Array.isArray(navigator.languages) ? navigator.languages : []),
|
|
667
|
+
].filter((tag): tag is string => Boolean(tag));
|
|
668
|
+
|
|
669
|
+
for (const candidate of candidates) {
|
|
670
|
+
const resolved = resolvePreferredLanguage(candidate);
|
|
671
|
+
if (resolved) {
|
|
672
|
+
return resolved;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (!configuredLanguages() && candidates[0]) {
|
|
677
|
+
return candidates[0];
|
|
678
|
+
}
|
|
679
|
+
return defaultLanguage;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ============================================================================
|
|
683
|
+
// Current language
|
|
684
|
+
// ============================================================================
|
|
685
|
+
|
|
686
|
+
function dispatchLanguageChanged(language: LanguageCode): void {
|
|
687
|
+
if (typeof window === "undefined") {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
window.dispatchEvent(
|
|
691
|
+
new CustomEvent("language-changed", {
|
|
692
|
+
detail: { language },
|
|
693
|
+
bubbles: true,
|
|
694
|
+
composed: true,
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Create a reactive signal for the current language (Portfolio pattern)
|
|
700
|
+
export const currentLanguage = {
|
|
701
|
+
value: readStoredLanguage() || getBrowserLanguage(),
|
|
702
|
+
set: function (lang: LanguageCode) {
|
|
703
|
+
this.value = lang;
|
|
704
|
+
storeLanguage(lang);
|
|
705
|
+
void loadLanguage(lang);
|
|
706
|
+
// What reads the document's language (dates, screen readers, hyphenation)
|
|
707
|
+
// follows too, rather than keeping the language the page was opened in.
|
|
708
|
+
if (typeof document !== "undefined" && document.documentElement) {
|
|
709
|
+
document.documentElement.lang = lang;
|
|
710
|
+
}
|
|
711
|
+
dispatchLanguageChanged(lang);
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Set the language module up for a product (see `I18nOptions`). Call it
|
|
717
|
+
* straight after importing the module: the first translations are fetched
|
|
718
|
+
* once the code that imported it has run. Called later, it fetches the current
|
|
719
|
+
* language's file if one is now configured.
|
|
720
|
+
*/
|
|
721
|
+
export function configureI18n(next: I18nOptions): void {
|
|
722
|
+
const previousKey = storageKey();
|
|
723
|
+
const previousLanguages = i18nOptions.languages;
|
|
724
|
+
i18nOptions = { ...i18nOptions, ...next };
|
|
725
|
+
|
|
726
|
+
applyRouting();
|
|
727
|
+
|
|
728
|
+
if (
|
|
729
|
+
storageKey() !== previousKey ||
|
|
730
|
+
i18nOptions.languages !== previousLanguages
|
|
731
|
+
) {
|
|
732
|
+
// The stored choice under the new key, or the browser's language, as one
|
|
733
|
+
// the product speaks
|
|
734
|
+
const stored = readStoredLanguage();
|
|
735
|
+
let language: LanguageCode = stored || getBrowserLanguage();
|
|
736
|
+
if (stored && configuredLanguages()) {
|
|
737
|
+
language = resolvePreferredLanguage(stored) ?? defaultLanguage;
|
|
738
|
+
}
|
|
739
|
+
if (language !== currentLanguage.value) {
|
|
740
|
+
currentLanguage.value = language;
|
|
741
|
+
dispatchLanguageChanged(language);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (next.sharedWords) {
|
|
746
|
+
void loadSharedWords();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (loadAttempted && i18nOptions.languageFile) {
|
|
750
|
+
void loadLanguage(currentLanguage.value);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Address prefixes follow the product's languages unless routing names its own. */
|
|
755
|
+
function applyRouting(): void {
|
|
756
|
+
if (!i18nOptions.routing && !i18nOptions.languages) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
configureLanguageRouting({
|
|
760
|
+
...(i18nOptions.languages ? { languages: i18nOptions.languages } : {}),
|
|
761
|
+
...i18nOptions.routing,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
let sharedWordsLoad: Promise<void> | null = null;
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Load Design system 1's shared interface words beneath the product's own
|
|
769
|
+
* (what `configureI18n({ sharedWords: true })` does). They arrive in a chunk
|
|
770
|
+
* of their own, so products that do not use them do not carry them.
|
|
771
|
+
*/
|
|
772
|
+
export function loadSharedWords(): Promise<void> {
|
|
773
|
+
if (!sharedWordsLoad) {
|
|
774
|
+
sharedWordsLoad = import("./shared-words").then(({ SHARED_WORDS }) => {
|
|
775
|
+
sharedWords = SHARED_WORDS;
|
|
776
|
+
if (typeof window !== "undefined") {
|
|
777
|
+
window.dispatchEvent(new CustomEvent("translations-ready"));
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return sharedWordsLoad;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
applyRouting();
|
|
785
|
+
if (i18nOptions.sharedWords) {
|
|
786
|
+
void loadSharedWords();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Auto-load translations when this module is imported (for CDN bundle).
|
|
790
|
+
// Waits a task, so a product can configure the module first, even after a
|
|
791
|
+
// dynamic import.
|
|
792
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
793
|
+
const start = () => {
|
|
794
|
+
if (i18nOptions.autoLoad !== false) void loadExternalTranslations();
|
|
795
|
+
};
|
|
796
|
+
if (document.readyState === "loading") {
|
|
797
|
+
document.addEventListener("DOMContentLoaded", start);
|
|
798
|
+
} else {
|
|
799
|
+
// DOM is already ready
|
|
800
|
+
setTimeout(start, 0);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Listen for external translations being loaded
|
|
805
|
+
if (typeof window !== "undefined") {
|
|
806
|
+
window.addEventListener("translations-ready", () => {
|
|
807
|
+
// Dispatch that translations are loaded
|
|
808
|
+
window.dispatchEvent(new CustomEvent("translations-loaded"));
|
|
809
|
+
|
|
810
|
+
// Dispatch language-changed to update all components
|
|
811
|
+
dispatchLanguageChanged(currentLanguage.value);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// Initialize translations on module load
|
|
815
|
+
// Use setTimeout to give other parts of the app time to set up event listeners first
|
|
816
|
+
setTimeout(() => {
|
|
817
|
+
if (typeof window === "undefined") {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
window.dispatchEvent(new CustomEvent("translations-loaded"));
|
|
821
|
+
|
|
822
|
+
// Also dispatch language-changed with the current language
|
|
823
|
+
dispatchLanguageChanged(currentLanguage.value);
|
|
824
|
+
}, 100);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// ============================================================================
|
|
828
|
+
// Looking words up
|
|
829
|
+
// ============================================================================
|
|
830
|
+
|
|
831
|
+
// Missing keys are ordinary: the same components carry what people write
|
|
832
|
+
// themselves, and a list called "Iroco.com" is not a key anyone forgot. So the
|
|
833
|
+
// console stays quiet unless someone is hunting for a missing key, and then
|
|
834
|
+
// each key is only reported once.
|
|
835
|
+
const REPORTED_MISSING_KEYS = new Set<string>();
|
|
836
|
+
|
|
837
|
+
// Loading is quiet too: what loaded, and from where, is only said when
|
|
838
|
+
// someone has turned the debug switch on.
|
|
839
|
+
function debugLog(message: string): void {
|
|
840
|
+
if (!debugSwitchOn()) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
console.info(`[Design system 1] ${message}`);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function debugSwitchOn(): boolean {
|
|
847
|
+
if (typeof window === "undefined") {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
if (window.DS_ONE_I18N_DEBUG === true) {
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
return storage()?.getItem(DEBUG_STORAGE_KEY) === "on";
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function missingKeyReportingOn(): boolean {
|
|
861
|
+
const option = i18nOptions.reportMissingKeys;
|
|
862
|
+
try {
|
|
863
|
+
if (typeof option === "function" ? option() : option) {
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
} catch {
|
|
867
|
+
// A failing check reports nothing
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return debugSwitchOn();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function reportMissingKey(key: string, language: LanguageCode): void {
|
|
874
|
+
if (!missingKeyReportingOn()) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const seen = `${language}:${key}`;
|
|
878
|
+
if (REPORTED_MISSING_KEYS.has(seen)) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
REPORTED_MISSING_KEYS.add(seen);
|
|
882
|
+
console.warn(
|
|
883
|
+
`[Design system 1 (Internationalization)] No translation found for key "${key}" in "${language}"`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* A key's text in the current language, with its placeholders (`{name}` and
|
|
889
|
+
* `{{name}}` alike) filled from `params`.
|
|
890
|
+
*/
|
|
891
|
+
export function translate(key: string, params?: TranslationParams): string {
|
|
892
|
+
return textInLanguage(key, currentLanguage.value, params);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* A key's text in a given language, whatever the page's own language is (a
|
|
897
|
+
* drawing made for one language, say). The fallback order: the language, its
|
|
898
|
+
* primary subtag (`ja-JP` reads `ja`), its relatives (`nn` reads `nb`,
|
|
899
|
+
* `pt-BR` reads `pt`), English, then the key itself. In each, the product's
|
|
900
|
+
* words come before the shared words.
|
|
901
|
+
*/
|
|
902
|
+
export function textInLanguage(
|
|
903
|
+
key: string,
|
|
904
|
+
language: LanguageCode,
|
|
905
|
+
params?: TranslationParams
|
|
906
|
+
): string {
|
|
907
|
+
const data = getTranslationData();
|
|
908
|
+
const resolved = resolveText(key, language, [data, sharedWords], params);
|
|
909
|
+
if (resolved.language !== null) {
|
|
910
|
+
return resolved.text;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Loaded a language at a time, English may not have arrived yet
|
|
914
|
+
const codes = fallbackLanguages(language);
|
|
915
|
+
if (
|
|
916
|
+
!codes.includes(defaultLanguage) &&
|
|
917
|
+
!data?.[defaultLanguage] &&
|
|
918
|
+
codes.some((code) => data?.[code])
|
|
919
|
+
) {
|
|
920
|
+
void loadLanguage(defaultLanguage);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
reportMissingKey(key, codes[codes.length - 1] ?? language);
|
|
924
|
+
return resolved.text;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
export function hasTranslation(
|
|
928
|
+
key: string,
|
|
929
|
+
language: LanguageCode = currentLanguage.value
|
|
930
|
+
): boolean {
|
|
931
|
+
if (!key) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const codes = fallbackLanguages(language);
|
|
936
|
+
if (!codes.includes(defaultLanguage)) {
|
|
937
|
+
codes.push(defaultLanguage);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return [getTranslationData(), sharedWords].some((source) =>
|
|
941
|
+
codes.some((code) => {
|
|
942
|
+
const langData = source?.[code];
|
|
943
|
+
return Boolean(
|
|
944
|
+
langData && Object.prototype.hasOwnProperty.call(langData, key)
|
|
945
|
+
);
|
|
946
|
+
})
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Get text - synchronous version for components
|
|
951
|
+
export function getText(key: string, params?: TranslationParams): string {
|
|
952
|
+
return translate(key, params);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Get available languages: the configured ones, else the ones in the loaded words
|
|
956
|
+
export function getAvailableLanguages(): Promise<LanguageCode[]> {
|
|
957
|
+
return Promise.resolve(getAvailableLanguagesSync());
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Synchronous version for immediate use
|
|
961
|
+
export function getAvailableLanguagesSync(): LanguageCode[] {
|
|
962
|
+
if (i18nOptions.languages && i18nOptions.languages.length > 0) {
|
|
963
|
+
return sortLanguageCodes([...i18nOptions.languages]);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const currentData = getTranslationData();
|
|
967
|
+
|
|
968
|
+
if (currentData && Object.keys(currentData).length > 0) {
|
|
969
|
+
return sortLanguageCodes(Object.keys(currentData));
|
|
970
|
+
}
|
|
971
|
+
return [defaultLanguage];
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Add words to what is loaded: one language's (`loadTranslations("da", {…})`)
|
|
976
|
+
* or several at once (`loadTranslations({ en: {…}, da: {…} })`), from a JSON
|
|
977
|
+
* file or a TypeScript module of the same shape (a `Partial` table is fine).
|
|
978
|
+
* Components update as they do when a translation file arrives.
|
|
979
|
+
*/
|
|
980
|
+
export function loadTranslations(
|
|
981
|
+
language: LanguageCode,
|
|
982
|
+
translations: TranslationTable
|
|
983
|
+
): void;
|
|
984
|
+
export function loadTranslations(translations: TranslationsFile): void;
|
|
985
|
+
export function loadTranslations(
|
|
986
|
+
languageOrMap: LanguageCode | TranslationsFile,
|
|
987
|
+
translations?: TranslationTable
|
|
988
|
+
): void {
|
|
989
|
+
const map: TranslationsFile =
|
|
990
|
+
typeof languageOrMap === "string"
|
|
991
|
+
? { [languageOrMap]: translations ?? {} }
|
|
992
|
+
: languageOrMap;
|
|
993
|
+
|
|
994
|
+
if (!validateTranslationMap(map)) {
|
|
995
|
+
console.warn(
|
|
996
|
+
"[Design system 1] loadTranslations expects words keyed by language code."
|
|
997
|
+
);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const data = getTranslationData();
|
|
1002
|
+
const merged: TranslationMap = {};
|
|
1003
|
+
for (const [code, words] of Object.entries(map)) {
|
|
1004
|
+
const defined: TranslationData = {};
|
|
1005
|
+
for (const [key, text] of Object.entries(words ?? {})) {
|
|
1006
|
+
if (typeof text === "string") {
|
|
1007
|
+
defined[key] = text;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
merged[code] = { ...data[code], ...defined };
|
|
1011
|
+
}
|
|
1012
|
+
mergeTranslations(merged);
|
|
1013
|
+
|
|
1014
|
+
if (typeof window !== "undefined") {
|
|
1015
|
+
window.dispatchEvent(new CustomEvent("translations-ready"));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Set language (Portfolio pattern)
|
|
1020
|
+
export function setLanguage(language: LanguageCode): void {
|
|
1021
|
+
// The signal owns persistence, loading, document language and notification.
|
|
1022
|
+
currentLanguage.set(language);
|
|
1023
|
+
}
|