@agent-analytics/shared-ui 0.2.0 → 0.3.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.
@@ -0,0 +1,154 @@
1
+ export const DEFAULT_LOCALE = 'en';
2
+ export const SUPPORTED_LOCALES = ['en', 'he', 'zh'];
3
+ export const LOCALE_COOKIE_NAME = 'aa_locale';
4
+ export const LOCALE_COOKIE_DOMAIN = '.agentanalytics.sh';
5
+ export const LOCALE_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
6
+
7
+ export const LOCALE_META = Object.freeze({
8
+ en: {
9
+ id: 'en',
10
+ label: 'English',
11
+ nativeLabel: 'English',
12
+ lang: 'en',
13
+ dir: 'ltr',
14
+ },
15
+ he: {
16
+ id: 'he',
17
+ label: 'Hebrew',
18
+ nativeLabel: 'עברית',
19
+ lang: 'he',
20
+ dir: 'rtl',
21
+ },
22
+ zh: {
23
+ id: 'zh',
24
+ label: 'Chinese',
25
+ nativeLabel: '简体中文',
26
+ lang: 'zh-CN',
27
+ dir: 'ltr',
28
+ },
29
+ });
30
+
31
+ export function normalizeLocale(locale) {
32
+ if (!locale) return DEFAULT_LOCALE;
33
+ const value = String(locale).trim().toLowerCase();
34
+ if (value === 'iw') return 'he';
35
+ if (value === 'zh-cn' || value === 'zh-hans' || value === 'zh-sg') return 'zh';
36
+ if (SUPPORTED_LOCALES.includes(value)) return value;
37
+ return DEFAULT_LOCALE;
38
+ }
39
+
40
+ export function getLocaleMeta(locale) {
41
+ return LOCALE_META[normalizeLocale(locale)];
42
+ }
43
+
44
+ export function getLocalePrefix(locale) {
45
+ const normalized = normalizeLocale(locale);
46
+ return normalized === DEFAULT_LOCALE ? '' : `/${normalized}`;
47
+ }
48
+
49
+ export function stripLocalePrefix(pathname = '/') {
50
+ const source = pathname || '/';
51
+ return source.replace(/^\/(?:he|zh)(?=\/|$)/, '') || '/';
52
+ }
53
+
54
+ export function localizePath(locale, pathname = '/') {
55
+ const [pathAndQuery, hash = ''] = String(pathname || '/').split('#');
56
+ const [pathOnly, query = ''] = pathAndQuery.split('?');
57
+ const normalizedPath = stripLocalePrefix(
58
+ pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`
59
+ );
60
+ const prefix = getLocalePrefix(locale);
61
+ const localizedPath =
62
+ prefix === ''
63
+ ? normalizedPath
64
+ : normalizedPath === '/'
65
+ ? `${prefix}/`
66
+ : `${prefix}${normalizedPath}`;
67
+
68
+ const search = query ? `?${query}` : '';
69
+ const fragment = hash ? `#${hash}` : '';
70
+ return `${localizedPath}${search}${fragment}`;
71
+ }
72
+
73
+ export function withLocaleUrl(baseUrl, locale, pathname = '/') {
74
+ return new URL(localizePath(locale, pathname), baseUrl).toString();
75
+ }
76
+
77
+ export function detectPreferredLocale(languages = []) {
78
+ for (const language of languages) {
79
+ const value = String(language || '').toLowerCase();
80
+ if (value.startsWith('he') || value.startsWith('iw')) return 'he';
81
+ if (value.startsWith('zh')) return 'zh';
82
+ }
83
+ return DEFAULT_LOCALE;
84
+ }
85
+
86
+ export function readLocaleCookie(cookieString = '') {
87
+ const match = String(cookieString)
88
+ .split(';')
89
+ .map((part) => part.trim())
90
+ .find((part) => part.startsWith(`${LOCALE_COOKIE_NAME}=`));
91
+
92
+ if (!match) return null;
93
+ return normalizeLocale(match.slice(`${LOCALE_COOKIE_NAME}=`.length));
94
+ }
95
+
96
+ export function resolveClientLocale(options = {}) {
97
+ const cookieValue =
98
+ options.cookieString ??
99
+ (typeof document !== 'undefined' ? document.cookie : '');
100
+ const fromCookie = readLocaleCookie(cookieValue);
101
+ if (fromCookie) return fromCookie;
102
+
103
+ const languages =
104
+ options.languages ??
105
+ (typeof navigator !== 'undefined'
106
+ ? navigator.languages || [navigator.language]
107
+ : []);
108
+
109
+ return detectPreferredLocale(languages);
110
+ }
111
+
112
+ export function getLocaleCookieDomain(
113
+ targetLocation = typeof location !== 'undefined' ? location : null
114
+ ) {
115
+ const hostname = targetLocation?.hostname || '';
116
+ if (hostname === 'agentanalytics.sh' || hostname.endsWith('.agentanalytics.sh')) {
117
+ return LOCALE_COOKIE_DOMAIN;
118
+ }
119
+ return '';
120
+ }
121
+
122
+ export function buildLocaleCookie(locale, options = {}) {
123
+ const normalized = normalizeLocale(locale);
124
+ const domain = options.domain ?? getLocaleCookieDomain();
125
+ const maxAge = options.maxAge ?? LOCALE_COOKIE_MAX_AGE;
126
+ const secure =
127
+ options.secure ??
128
+ (typeof location !== 'undefined' ? location.protocol === 'https:' : true);
129
+
130
+ const parts = [
131
+ `${LOCALE_COOKIE_NAME}=${normalized}`,
132
+ 'Path=/',
133
+ `Max-Age=${maxAge}`,
134
+ 'SameSite=Lax',
135
+ ];
136
+
137
+ if (domain) parts.push(`Domain=${domain}`);
138
+ if (secure) parts.push('Secure');
139
+ return parts.join('; ');
140
+ }
141
+
142
+ export function writeLocaleCookie(locale, options = {}) {
143
+ if (typeof document === 'undefined') return normalizeLocale(locale);
144
+ document.cookie = buildLocaleCookie(locale, options);
145
+ return normalizeLocale(locale);
146
+ }
147
+
148
+ export function setDocumentLocale(locale, targetDocument = typeof document !== 'undefined' ? document : null) {
149
+ if (!targetDocument?.documentElement) return getLocaleMeta(locale);
150
+ const meta = getLocaleMeta(locale);
151
+ targetDocument.documentElement.lang = meta.lang;
152
+ targetDocument.documentElement.dir = meta.dir;
153
+ return meta;
154
+ }
package/dist/recipes.css CHANGED
@@ -99,11 +99,50 @@
99
99
  font-size: 14px;
100
100
  }
101
101
 
102
+ .aa-utility-header__controls {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ gap: 12px;
106
+ }
107
+
102
108
  .aa-utility-header__link {
103
109
  text-decoration: none;
104
110
  transition: color 0.2s ease;
105
111
  }
106
112
 
113
+ .aa-utility-header__locale {
114
+ display: inline-flex;
115
+ align-items: center;
116
+ gap: 6px;
117
+ padding: 4px;
118
+ border-radius: 999px;
119
+ border: 1px solid var(--surface-border);
120
+ background: var(--surface-glass);
121
+ box-shadow: var(--shadow-soft);
122
+ }
123
+
124
+ .aa-utility-header__locale-option {
125
+ min-height: 34px;
126
+ padding: 0 10px;
127
+ border: 0;
128
+ border-radius: 999px;
129
+ background: transparent;
130
+ color: var(--text-dim);
131
+ font: inherit;
132
+ line-height: 1;
133
+ cursor: pointer;
134
+ transition: background 0.18s ease, color 0.18s ease;
135
+ }
136
+
137
+ .aa-utility-header__locale-option:hover {
138
+ color: var(--text);
139
+ }
140
+
141
+ .aa-utility-header__locale-option.is-active {
142
+ background: var(--button-dark-background);
143
+ color: var(--button-dark-text);
144
+ }
145
+
107
146
  .aa-utility-header__cta {
108
147
  display: inline-flex;
109
148
  align-items: center;
@@ -323,6 +362,20 @@
323
362
  color: var(--aa-footer-hover, var(--sl-color-white, var(--text)));
324
363
  }
325
364
 
365
+ [dir='rtl'] .aa-utility-header__inner,
366
+ [dir='rtl'] .aa-footer__grid,
367
+ [dir='rtl'] .aa-footer__sections {
368
+ direction: rtl;
369
+ }
370
+
371
+ [dir='rtl'] .aa-utility-header__copy,
372
+ [dir='rtl'] .aa-footer__brand-copy,
373
+ [dir='rtl'] .aa-footer__section,
374
+ [dir='rtl'] .aa-footer__description,
375
+ [dir='rtl'] .aa-footer__copy {
376
+ text-align: right;
377
+ }
378
+
326
379
  @media (max-width: 960px) {
327
380
  .aa-footer__grid {
328
381
  grid-template-columns: 1fr;
@@ -358,6 +411,15 @@
358
411
  gap: 12px;
359
412
  }
360
413
 
414
+ .aa-utility-header__controls {
415
+ gap: 8px;
416
+ }
417
+
418
+ .aa-utility-header__locale-option {
419
+ min-height: 32px;
420
+ padding: 0 8px;
421
+ }
422
+
361
423
  .aa-utility-header__link {
362
424
  display: none;
363
425
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-analytics/shared-ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Shared UI primitives, tokens, and marketing chrome for Agent Analytics properties.",
@@ -32,6 +32,7 @@
32
32
  ".": "./dist/index.js",
33
33
  "./header": "./dist/header.js",
34
34
  "./footer": "./dist/footer.js",
35
+ "./locales": "./dist/locales.js",
35
36
  "./astro/Footer.astro": "./dist/astro/Footer.astro",
36
37
  "./eleventy/header.njk": "./dist/eleventy/header.njk",
37
38
  "./eleventy/footer.njk": "./dist/eleventy/footer.njk",