@analogjs/router 2.5.0-beta.5 → 2.5.0-beta.50

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,229 @@
1
+ import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, assertInInjectionContext } from '@angular/core';
2
+ import { LOCALE, REQUEST } from '@analogjs/router/tokens';
3
+
4
+ /**
5
+ * Injection token for the resolved i18n configuration.
6
+ * Provided by `provideI18n()` and consumed by `injectSwitchLocale()`.
7
+ * @internal
8
+ */
9
+ const I18N_CONFIG = new InjectionToken('@analogjs/router I18n Config');
10
+ /**
11
+ * Resolves the full i18n config by merging explicit values with
12
+ * build-time globals injected by the platform plugin.
13
+ */
14
+ function resolveI18nConfig(config) {
15
+ const defaultLocale = config.defaultLocale ??
16
+ (typeof ANALOG_I18N_DEFAULT_LOCALE !== 'undefined'
17
+ ? ANALOG_I18N_DEFAULT_LOCALE
18
+ : undefined);
19
+ const locales = config.locales ??
20
+ (typeof ANALOG_I18N_LOCALES !== 'undefined'
21
+ ? ANALOG_I18N_LOCALES
22
+ : undefined);
23
+ if (!defaultLocale || !locales) {
24
+ throw new Error('[@analogjs/router] provideI18n() requires defaultLocale and locales. ' +
25
+ 'Either pass them explicitly or configure i18n in the analog() plugin in vite.config.ts.');
26
+ }
27
+ return { defaultLocale, locales, loader: config.loader };
28
+ }
29
+ /**
30
+ * Provides runtime i18n support using Angular's $localize.
31
+ *
32
+ * This provider:
33
+ * 1. Detects the active locale from the URL or falls back to the default.
34
+ * 2. Makes the current locale available via the LOCALE injection token.
35
+ * 3. Loads translations for the active locale at startup using $localize.
36
+ *
37
+ * Works in both SSR and client-only modes. On the client, locale is detected
38
+ * from `window.location.pathname`. On the server, locale is detected from
39
+ * the request in `provideServerContext()` and provided at the platform level;
40
+ * this function does not shadow it.
41
+ *
42
+ * When the platform plugin is configured with `i18n` in `vite.config.ts`,
43
+ * `defaultLocale` and `locales` are injected automatically — only
44
+ * `loader` is required:
45
+ *
46
+ * ```typescript
47
+ * provideI18n({
48
+ * loader: (locale) => import(`./i18n/${locale}.json`),
49
+ * })
50
+ * ```
51
+ */
52
+ function provideI18n(config) {
53
+ const resolved = resolveI18nConfig(config);
54
+ // Only provide LOCALE at the environment level on the client. On the
55
+ // server, the platform-level LOCALE set by `provideServerContext()` is
56
+ // authoritative and must not be shadowed by an environment-level provider.
57
+ const localeProviders = typeof window !== 'undefined'
58
+ ? [{ provide: LOCALE, useValue: detectClientLocale(resolved) }]
59
+ : [];
60
+ return makeEnvironmentProviders([
61
+ { provide: I18N_CONFIG, useValue: resolved },
62
+ ...localeProviders,
63
+ provideAppInitializer(async () => {
64
+ const locale = resolveActiveLocale(resolved);
65
+ await initI18n(resolved, locale);
66
+ }),
67
+ ]);
68
+ }
69
+ /**
70
+ * Resolves the active locale, preferring the injected `LOCALE` token
71
+ * (which on the server reads from the platform-level provider set by
72
+ * `provideServerContext()`) and falling back to the request URL,
73
+ * `window.location.pathname`, or `defaultLocale`.
74
+ */
75
+ function resolveActiveLocale(config) {
76
+ const injected = inject(LOCALE, { optional: true });
77
+ if (injected && config.locales.includes(injected)) {
78
+ return injected;
79
+ }
80
+ // Fallback: read the path directly from the request on the server or
81
+ // from the browser URL on the client. This covers cases where a locale
82
+ // prefix is present in the URL but no token provider set it explicitly.
83
+ const req = inject(REQUEST, { optional: true });
84
+ const pathname = req?.originalUrl ??
85
+ req?.url ??
86
+ (typeof window !== 'undefined' ? window.location.pathname : '/');
87
+ const first = pathname.split('?')[0].split('/').filter(Boolean)[0];
88
+ if (first && config.locales.includes(first)) {
89
+ return first;
90
+ }
91
+ return config.defaultLocale;
92
+ }
93
+ /**
94
+ * Detects the locale on the client from the URL path prefix.
95
+ * Returns the default locale on the server or when no match is found.
96
+ */
97
+ function detectClientLocale(config) {
98
+ if (typeof window === 'undefined') {
99
+ return config.defaultLocale;
100
+ }
101
+ const pathname = window.location.pathname;
102
+ const segments = pathname.split('/').filter(Boolean);
103
+ const firstSegment = segments[0];
104
+ if (firstSegment && config.locales.includes(firstSegment)) {
105
+ return firstSegment;
106
+ }
107
+ return config.defaultLocale;
108
+ }
109
+ /**
110
+ * Loads translations for the given locale and registers them with $localize.
111
+ *
112
+ * Always clears any previously loaded translations first so that switching
113
+ * between locales in a single SSR process does not mix translation maps.
114
+ */
115
+ async function initI18n(config, locale) {
116
+ const activeLocale = locale ?? config.defaultLocale;
117
+ await clearTranslationsRuntime();
118
+ // The source locale (first entry in `locales`) has its messages baked
119
+ // directly into the template source, so there is nothing to load.
120
+ if (activeLocale === config.locales[0]) {
121
+ return;
122
+ }
123
+ const translations = await config.loader(activeLocale);
124
+ if (translations && Object.keys(translations).length > 0) {
125
+ await loadTranslationsRuntime(translations);
126
+ }
127
+ }
128
+ /**
129
+ * Loads translations into the global $localize translation map.
130
+ *
131
+ * Uses `@angular/localize`'s `loadTranslations` when available so that
132
+ * each translation string is parsed into the `{text, messageParts,
133
+ * placeholderNames}` shape that `$localize.translate` expects. Falls back
134
+ * to writing raw strings only as a last resort (in which case lookups
135
+ * will not succeed — the fallback exists to keep error messages useful
136
+ * for users who have not installed `@angular/localize`).
137
+ *
138
+ * Requires `@angular/localize/init` to be imported in the application
139
+ * entry point so that `globalThis.$localize` is defined.
140
+ */
141
+ async function loadTranslationsRuntime(translations) {
142
+ const $localize = globalThis.$localize;
143
+ if (!$localize) {
144
+ console.warn('[@analogjs/router] $localize is not available. ' +
145
+ 'Make sure to import @angular/localize/init in your application entry point.');
146
+ return;
147
+ }
148
+ try {
149
+ const { loadTranslations } = (await import('@angular/localize'));
150
+ loadTranslations(translations);
151
+ }
152
+ catch {
153
+ console.warn('[@analogjs/router] Unable to import @angular/localize. ' +
154
+ 'Install it as a dependency to enable runtime translation loading.');
155
+ $localize.TRANSLATIONS ??= {};
156
+ for (const [id, message] of Object.entries(translations)) {
157
+ $localize.TRANSLATIONS[id] = message;
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Clears any previously loaded translations from `$localize` so that the
163
+ * next render falls through to the source messages until new translations
164
+ * are loaded. Uses `@angular/localize`'s `clearTranslations` when present.
165
+ */
166
+ /** @internal — exported for tests; not re-exported from the package entry. */
167
+ async function clearTranslationsRuntime() {
168
+ const $localize = globalThis.$localize;
169
+ if (!$localize) {
170
+ return;
171
+ }
172
+ try {
173
+ const { clearTranslations } = (await import('@angular/localize'));
174
+ clearTranslations();
175
+ }
176
+ catch {
177
+ $localize.translate = undefined;
178
+ $localize.TRANSLATIONS = {};
179
+ }
180
+ }
181
+ /**
182
+ * Returns an injectable function that switches the application locale.
183
+ * Reads the configured locales from the I18N_CONFIG token provided
184
+ * by `provideI18n()`.
185
+ *
186
+ * Triggers a full page navigation to the new locale URL so that
187
+ * all $localize templates re-evaluate with the correct translations.
188
+ *
189
+ * Usage:
190
+ * ```typescript
191
+ * const switchLang = injectSwitchLocale();
192
+ * switchLang('fr'); // navigates to /fr/current-path
193
+ * ```
194
+ */
195
+ function injectSwitchLocale() {
196
+ assertInInjectionContext(injectSwitchLocale);
197
+ const config = inject(I18N_CONFIG);
198
+ return (targetLocale) => {
199
+ if (typeof window === 'undefined') {
200
+ return;
201
+ }
202
+ const { pathname, search, hash } = window.location;
203
+ const newPath = replaceLocaleInPath(pathname, targetLocale, config.locales);
204
+ window.location.href = `${newPath}${search}${hash}`;
205
+ };
206
+ }
207
+ /**
208
+ * Replaces or inserts the locale prefix in a URL path.
209
+ *
210
+ * - If the path starts with a known locale, it is swapped.
211
+ * - If no locale prefix exists, the target locale is prepended.
212
+ */
213
+ function replaceLocaleInPath(pathname, targetLocale, locales) {
214
+ const segments = pathname.split('/').filter(Boolean);
215
+ if (segments.length > 0 && locales.includes(segments[0])) {
216
+ segments[0] = targetLocale;
217
+ }
218
+ else {
219
+ segments.unshift(targetLocale);
220
+ }
221
+ return '/' + segments.join('/');
222
+ }
223
+
224
+ /**
225
+ * Generated bundle index. Do not edit.
226
+ */
227
+
228
+ export { injectSwitchLocale, loadTranslationsRuntime, provideI18n };
229
+ //# sourceMappingURL=analogjs-router-i18n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analogjs-router-i18n.mjs","sources":["../../../../packages/router/i18n/src/provide-i18n.ts","../../../../packages/router/i18n/src/analogjs-router-i18n.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n InjectionToken,\n assertInInjectionContext,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { LOCALE, REQUEST, ServerRequest } from '@analogjs/router/tokens';\n\ndeclare const ANALOG_I18N_DEFAULT_LOCALE: string;\ndeclare const ANALOG_I18N_LOCALES: string[];\n\n/**\n * Configuration for runtime i18n support.\n *\n * `defaultLocale` and `locales` are optional when the platform plugin\n * is configured with `i18n` in `vite.config.ts` — the values are\n * injected as build-time globals automatically.\n */\nexport interface I18nConfig {\n /**\n * The default locale to use when no locale is detected.\n * If omitted, reads from the platform plugin's `i18n.defaultLocale`.\n */\n defaultLocale?: string;\n\n /**\n * List of supported locale identifiers.\n * If omitted, reads from the platform plugin's `i18n.locales`.\n */\n locales?: string[];\n\n /**\n * A function that returns translations for a given locale.\n * The returned record maps message IDs to translated strings.\n */\n loader: (\n locale: string,\n ) => Promise<Record<string, string>> | Record<string, string>;\n}\n\n/**\n * Fully resolved i18n config with all required fields.\n */\nexport type ResolvedI18nConfig = Required<I18nConfig>;\n\n/**\n * Injection token for the resolved i18n configuration.\n * Provided by `provideI18n()` and consumed by `injectSwitchLocale()`.\n * @internal\n */\nconst I18N_CONFIG = new InjectionToken<ResolvedI18nConfig>(\n '@analogjs/router I18n Config',\n);\n\n/**\n * Resolves the full i18n config by merging explicit values with\n * build-time globals injected by the platform plugin.\n */\nexport function resolveI18nConfig(config: I18nConfig): Required<I18nConfig> {\n const defaultLocale =\n config.defaultLocale ??\n (typeof ANALOG_I18N_DEFAULT_LOCALE !== 'undefined'\n ? ANALOG_I18N_DEFAULT_LOCALE\n : undefined);\n\n const locales =\n config.locales ??\n (typeof ANALOG_I18N_LOCALES !== 'undefined'\n ? ANALOG_I18N_LOCALES\n : undefined);\n\n if (!defaultLocale || !locales) {\n throw new Error(\n '[@analogjs/router] provideI18n() requires defaultLocale and locales. ' +\n 'Either pass them explicitly or configure i18n in the analog() plugin in vite.config.ts.',\n );\n }\n\n return { defaultLocale, locales, loader: config.loader };\n}\n\n/**\n * Provides runtime i18n support using Angular's $localize.\n *\n * This provider:\n * 1. Detects the active locale from the URL or falls back to the default.\n * 2. Makes the current locale available via the LOCALE injection token.\n * 3. Loads translations for the active locale at startup using $localize.\n *\n * Works in both SSR and client-only modes. On the client, locale is detected\n * from `window.location.pathname`. On the server, locale is detected from\n * the request in `provideServerContext()` and provided at the platform level;\n * this function does not shadow it.\n *\n * When the platform plugin is configured with `i18n` in `vite.config.ts`,\n * `defaultLocale` and `locales` are injected automatically — only\n * `loader` is required:\n *\n * ```typescript\n * provideI18n({\n * loader: (locale) => import(`./i18n/${locale}.json`),\n * })\n * ```\n */\nexport function provideI18n(config: I18nConfig): EnvironmentProviders {\n const resolved = resolveI18nConfig(config);\n\n // Only provide LOCALE at the environment level on the client. On the\n // server, the platform-level LOCALE set by `provideServerContext()` is\n // authoritative and must not be shadowed by an environment-level provider.\n const localeProviders =\n typeof window !== 'undefined'\n ? [{ provide: LOCALE, useValue: detectClientLocale(resolved) }]\n : [];\n\n return makeEnvironmentProviders([\n { provide: I18N_CONFIG, useValue: resolved },\n ...localeProviders,\n provideAppInitializer(async () => {\n const locale = resolveActiveLocale(resolved);\n await initI18n(resolved, locale);\n }),\n ]);\n}\n\n/**\n * Resolves the active locale, preferring the injected `LOCALE` token\n * (which on the server reads from the platform-level provider set by\n * `provideServerContext()`) and falling back to the request URL,\n * `window.location.pathname`, or `defaultLocale`.\n */\nfunction resolveActiveLocale(config: ResolvedI18nConfig): string {\n const injected = inject(LOCALE, { optional: true });\n if (injected && config.locales.includes(injected)) {\n return injected;\n }\n\n // Fallback: read the path directly from the request on the server or\n // from the browser URL on the client. This covers cases where a locale\n // prefix is present in the URL but no token provider set it explicitly.\n const req = inject(REQUEST, { optional: true }) as ServerRequest | null;\n const pathname =\n req?.originalUrl ??\n req?.url ??\n (typeof window !== 'undefined' ? window.location.pathname : '/');\n const first = pathname.split('?')[0].split('/').filter(Boolean)[0];\n if (first && config.locales.includes(first)) {\n return first;\n }\n\n return config.defaultLocale;\n}\n\n/**\n * Detects the locale on the client from the URL path prefix.\n * Returns the default locale on the server or when no match is found.\n */\nexport function detectClientLocale(config: ResolvedI18nConfig): string {\n if (typeof window === 'undefined') {\n return config.defaultLocale;\n }\n\n const pathname = window.location.pathname;\n const segments = pathname.split('/').filter(Boolean);\n const firstSegment = segments[0];\n\n if (firstSegment && config.locales.includes(firstSegment)) {\n return firstSegment;\n }\n\n return config.defaultLocale;\n}\n\n/**\n * Loads translations for the given locale and registers them with $localize.\n *\n * Always clears any previously loaded translations first so that switching\n * between locales in a single SSR process does not mix translation maps.\n */\nexport async function initI18n(\n config: ResolvedI18nConfig,\n locale?: string,\n): Promise<void> {\n const activeLocale = locale ?? config.defaultLocale;\n await clearTranslationsRuntime();\n\n // The source locale (first entry in `locales`) has its messages baked\n // directly into the template source, so there is nothing to load.\n if (activeLocale === config.locales[0]) {\n return;\n }\n\n const translations = await config.loader(activeLocale);\n if (translations && Object.keys(translations).length > 0) {\n await loadTranslationsRuntime(translations);\n }\n}\n\n/**\n * Loads translations into the global $localize translation map.\n *\n * Uses `@angular/localize`'s `loadTranslations` when available so that\n * each translation string is parsed into the `{text, messageParts,\n * placeholderNames}` shape that `$localize.translate` expects. Falls back\n * to writing raw strings only as a last resort (in which case lookups\n * will not succeed — the fallback exists to keep error messages useful\n * for users who have not installed `@angular/localize`).\n *\n * Requires `@angular/localize/init` to be imported in the application\n * entry point so that `globalThis.$localize` is defined.\n */\nexport async function loadTranslationsRuntime(\n translations: Record<string, string>,\n): Promise<void> {\n const $localize = (globalThis as any).$localize;\n if (!$localize) {\n console.warn(\n '[@analogjs/router] $localize is not available. ' +\n 'Make sure to import @angular/localize/init in your application entry point.',\n );\n return;\n }\n\n try {\n const { loadTranslations } = (await import('@angular/localize')) as {\n loadTranslations: (t: Record<string, string>) => void;\n };\n loadTranslations(translations);\n } catch {\n console.warn(\n '[@analogjs/router] Unable to import @angular/localize. ' +\n 'Install it as a dependency to enable runtime translation loading.',\n );\n $localize.TRANSLATIONS ??= {};\n for (const [id, message] of Object.entries(translations)) {\n $localize.TRANSLATIONS[id] = message;\n }\n }\n}\n\n/**\n * Clears any previously loaded translations from `$localize` so that the\n * next render falls through to the source messages until new translations\n * are loaded. Uses `@angular/localize`'s `clearTranslations` when present.\n */\n/** @internal — exported for tests; not re-exported from the package entry. */\nexport async function clearTranslationsRuntime(): Promise<void> {\n const $localize = (globalThis as any).$localize;\n if (!$localize) {\n return;\n }\n try {\n const { clearTranslations } = (await import('@angular/localize')) as {\n clearTranslations: () => void;\n };\n clearTranslations();\n } catch {\n $localize.translate = undefined;\n $localize.TRANSLATIONS = {};\n }\n}\n\n/**\n * Returns an injectable function that switches the application locale.\n * Reads the configured locales from the I18N_CONFIG token provided\n * by `provideI18n()`.\n *\n * Triggers a full page navigation to the new locale URL so that\n * all $localize templates re-evaluate with the correct translations.\n *\n * Usage:\n * ```typescript\n * const switchLang = injectSwitchLocale();\n * switchLang('fr'); // navigates to /fr/current-path\n * ```\n */\nexport function injectSwitchLocale(): (targetLocale: string) => void {\n assertInInjectionContext(injectSwitchLocale);\n const config = inject(I18N_CONFIG);\n\n return (targetLocale: string) => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const { pathname, search, hash } = window.location;\n const newPath = replaceLocaleInPath(pathname, targetLocale, config.locales);\n window.location.href = `${newPath}${search}${hash}`;\n };\n}\n\n/**\n * Replaces or inserts the locale prefix in a URL path.\n *\n * - If the path starts with a known locale, it is swapped.\n * - If no locale prefix exists, the target locale is prepended.\n */\nexport function replaceLocaleInPath(\n pathname: string,\n targetLocale: string,\n locales: string[],\n): string {\n const segments = pathname.split('/').filter(Boolean);\n\n if (segments.length > 0 && locales.includes(segments[0])) {\n segments[0] = targetLocale;\n } else {\n segments.unshift(targetLocale);\n }\n\n return '/' + segments.join('/');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  import { ɵresetCompiledComponents as _resetCompiledComponents, InjectionToken, assertInInjectionContext, inject, TransferState, makeStateKey, reflectComponentType, APP_ID, ɵConsole as _Console, enableProdMode } from '@angular/core';
2
2
  import { ɵSERVER_CONTEXT as _SERVER_CONTEXT, provideServerRendering, renderApplication } from '@angular/platform-server';
3
- import { REQUEST, RESPONSE, BASE_URL } from '@analogjs/router/tokens';
3
+ import { REQUEST, RESPONSE, BASE_URL, LOCALE } from '@analogjs/router/tokens';
4
4
  import { bootstrapApplication } from '@angular/platform-browser';
5
5
  import { getHeader, createEvent, readBody } from 'h3';
6
6
 
7
7
  function provideServerContext({ req, res, }) {
8
8
  const baseUrl = getBaseUrl(req);
9
+ const locale = detectLocale(req);
9
10
  if (import.meta.env.DEV) {
10
11
  _resetCompiledComponents();
11
12
  }
@@ -14,8 +15,56 @@ function provideServerContext({ req, res, }) {
14
15
  { provide: REQUEST, useValue: req },
15
16
  { provide: RESPONSE, useValue: res },
16
17
  { provide: BASE_URL, useValue: baseUrl },
18
+ ...(locale ? [{ provide: LOCALE, useValue: locale }] : []),
17
19
  ];
18
20
  }
21
+ /**
22
+ * Detects the locale from the request URL path prefix or Accept-Language header.
23
+ * URL prefix takes priority (e.g. /fr/about -> 'fr').
24
+ */
25
+ function detectLocale(req) {
26
+ const url = req.originalUrl || req.url || '';
27
+ const localeFromUrl = extractLocaleFromUrl(url);
28
+ if (localeFromUrl) {
29
+ return localeFromUrl;
30
+ }
31
+ return parseAcceptLanguage(req.headers['accept-language']);
32
+ }
33
+ /**
34
+ * Extracts a locale from the first URL path segment if it matches
35
+ * a BCP 47-like pattern (e.g. 'en', 'en-US', 'zh-Hans-CN').
36
+ */
37
+ function extractLocaleFromUrl(url) {
38
+ const pathname = url.split('?')[0];
39
+ const segments = pathname.split('/').filter(Boolean);
40
+ if (segments.length === 0) {
41
+ return undefined;
42
+ }
43
+ const firstSegment = segments[0];
44
+ // Match BCP 47 language tags: 2-letter language code with optional region/script
45
+ // e.g. 'en', 'en-US', 'zh-Hans', 'zh-Hans-CN'
46
+ if (/^[a-z]{2}(-[a-zA-Z]{2,4})?(-[a-zA-Z]{2}|\d{3})?$/.test(firstSegment)) {
47
+ return firstSegment;
48
+ }
49
+ return undefined;
50
+ }
51
+ /**
52
+ * Parses the Accept-Language header and returns the most preferred language.
53
+ */
54
+ function parseAcceptLanguage(header) {
55
+ if (!header) {
56
+ return undefined;
57
+ }
58
+ const locales = header
59
+ .split(',')
60
+ .map((part) => {
61
+ const [locale, qPart] = part.trim().split(';');
62
+ const q = qPart ? parseFloat(qPart.replace('q=', '')) : 1;
63
+ return { locale: locale.trim(), q };
64
+ })
65
+ .sort((a, b) => b.q - a.q);
66
+ return locales[0]?.locale || undefined;
67
+ }
19
68
  function getBaseUrl(req) {
20
69
  const protocol = getRequestProtocol(req);
21
70
  const { originalUrl, headers } = req;
@@ -162,6 +211,27 @@ function retrieveTransferredState(html, appId) {
162
211
  if (import.meta.env.PROD) {
163
212
  enableProdMode();
164
213
  }
214
+ /**
215
+ * Nulls `def.tView` on every component definition that Angular has
216
+ * compiled in this process. Angular caches the result of `consts()` on
217
+ * `def.tView` — that factory is where `$localize` tagged templates are
218
+ * evaluated — so without this reset the first rendered locale would be
219
+ * frozen into the cache for the process lifetime.
220
+ *
221
+ * The set on `globalThis.__ngComponentDefs` is populated by a Vite
222
+ * transform in `@analogjs/platform` that patches `@angular/core`'s
223
+ * `getComponentId()` to mirror every compiled component definition to
224
+ * a global Set, bypassing the `ngServerMode` guard that normally
225
+ * prevents registration on the server.
226
+ */
227
+ function resetComponentDefTViews() {
228
+ const defs = globalThis.__ngComponentDefs;
229
+ if (!defs)
230
+ return;
231
+ for (const def of defs) {
232
+ def.tView = null;
233
+ }
234
+ }
165
235
  /**
166
236
  * Returns a function that accepts the navigation URL,
167
237
  * the root HTML, and server context.
@@ -179,6 +249,7 @@ function render(rootComponent, config, platformProviders = []) {
179
249
  if (serverComponentRequest(serverContext)) {
180
250
  return await renderServerComponent(url, serverContext);
181
251
  }
252
+ resetComponentDefTViews();
182
253
  const html = await renderApplication(bootstrap, {
183
254
  document,
184
255
  url,
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-router-server.mjs","sources":["../../../../packages/router/server/src/provide-server-context.ts","../../../../packages/router/server/src/tokens.ts","../../../../packages/router/server/src/server-component-render.ts","../../../../packages/router/server/src/render.ts","../../../../packages/router/server/src/analogjs-router-server.ts"],"sourcesContent":["import { StaticProvider, ɵresetCompiledComponents } from '@angular/core';\nimport { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server';\n\nimport {\n BASE_URL,\n REQUEST,\n RESPONSE,\n ServerRequest,\n ServerResponse,\n} from '@analogjs/router/tokens';\n\nexport function provideServerContext({\n req,\n res,\n}: {\n req: ServerRequest;\n res: ServerResponse;\n}): StaticProvider[] {\n const baseUrl = getBaseUrl(req);\n\n if (import.meta.env.DEV) {\n ɵresetCompiledComponents();\n }\n\n return [\n { provide: SERVER_CONTEXT, useValue: 'ssr-analog' },\n { provide: REQUEST, useValue: req },\n { provide: RESPONSE, useValue: res },\n { provide: BASE_URL, useValue: baseUrl },\n ];\n}\n\nexport function getBaseUrl(req: ServerRequest) {\n const protocol = getRequestProtocol(req);\n const { originalUrl, headers } = req;\n const parsedUrl = new URL(\n '',\n `${protocol}://${headers.host}${\n originalUrl.endsWith('/')\n ? originalUrl.substring(0, originalUrl.length - 1)\n : originalUrl\n }`,\n );\n const baseUrl = parsedUrl.origin;\n\n return baseUrl;\n}\n\nexport function getRequestProtocol(\n req: ServerRequest,\n opts: { xForwardedProto?: boolean } = {},\n) {\n if (\n opts.xForwardedProto !== false &&\n req.headers['x-forwarded-proto'] === 'https'\n ) {\n return 'https';\n }\n\n return (req.connection as any)?.encrypted ? 'https' : 'http';\n}\n","import {\n assertInInjectionContext,\n inject,\n InjectionToken,\n makeStateKey,\n Provider,\n TransferState,\n} from '@angular/core';\n\nexport const STATIC_PROPS = new InjectionToken<Record<string, any>>(\n 'Static Props',\n);\n\nexport function provideStaticProps<T = Record<string, any>>(\n props: T,\n): Provider {\n return {\n provide: STATIC_PROPS,\n useFactory() {\n return props;\n },\n };\n}\n\nexport function injectStaticProps() {\n assertInInjectionContext(injectStaticProps);\n\n return inject(STATIC_PROPS);\n}\n\nexport function injectStaticOutputs<T>() {\n const transferState = inject(TransferState);\n const outputsKey = makeStateKey<T>('_analog_output');\n\n return {\n set(data: T) {\n transferState.set(outputsKey, data);\n },\n };\n}\n","import { ApplicationConfig, Type } from '@angular/core';\nimport {\n bootstrapApplication,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport {\n reflectComponentType,\n ɵConsole as Console,\n APP_ID,\n} from '@angular/core';\nimport {\n provideServerRendering,\n renderApplication,\n ɵSERVER_CONTEXT as SERVER_CONTEXT,\n} from '@angular/platform-server';\nimport { ServerContext } from '@analogjs/router/tokens';\nimport { createEvent, readBody, getHeader } from 'h3';\n\nimport { provideStaticProps } from './tokens';\n\ntype ComponentLoader = () => Promise<Type<unknown>>;\n\nexport function serverComponentRequest(serverContext: ServerContext) {\n const serverComponentId = getHeader(\n createEvent(serverContext.req, serverContext.res),\n 'X-Analog-Component',\n );\n\n if (\n !serverComponentId &&\n serverContext.req.url &&\n serverContext.req.url.startsWith('/_analog/components')\n ) {\n const componentId = serverContext.req.url.split('/')?.[3];\n\n return componentId;\n }\n\n return serverComponentId;\n}\n\nconst components = import.meta.glob([\n '/src/server/components/**/*.{ts,analog,ag}',\n]);\n\nexport async function renderServerComponent(\n url: string,\n serverContext: ServerContext,\n config?: ApplicationConfig,\n) {\n const componentReqId = serverComponentRequest(serverContext) as string;\n const { componentLoader, componentId } = getComponentLoader(componentReqId);\n\n if (!componentLoader) {\n return new Response(`Server Component Not Found ${componentId}`, {\n status: 404,\n });\n }\n\n const component = ((await componentLoader()) as any)[\n 'default'\n ] as Type<unknown>;\n\n if (!component) {\n return new Response(`No default export for ${componentId}`, {\n status: 422,\n });\n }\n\n const mirror = reflectComponentType(component);\n const selector = mirror?.selector.split(',')?.[0] || 'server-component';\n const event = createEvent(serverContext.req, serverContext.res);\n const body = (await readBody(event)) || {};\n const appId = `analog-server-${selector.toLowerCase()}-${new Date().getTime()}`;\n\n const bootstrap = (context?: BootstrapContext) =>\n bootstrapApplication(\n component,\n {\n providers: [\n provideServerRendering(),\n provideStaticProps(body),\n { provide: SERVER_CONTEXT, useValue: 'analog-server-component' },\n {\n provide: APP_ID,\n useFactory() {\n return appId;\n },\n },\n ...(config?.providers || []),\n ],\n },\n context,\n );\n\n const html = await renderApplication(bootstrap, {\n url,\n document: `<${selector}></${selector}>`,\n platformProviders: [\n {\n provide: Console,\n useFactory() {\n return {\n warn: () => {},\n log: () => {},\n };\n },\n },\n ],\n });\n\n const outputs = retrieveTransferredState(html, appId);\n const responseData: { html: string; outputs: Record<string, any> } = {\n html,\n outputs,\n };\n\n return new Response(JSON.stringify(responseData), {\n headers: {\n 'X-Analog-Component': 'true',\n },\n });\n}\n\nfunction getComponentLoader(componentReqId: string): {\n componentLoader: ComponentLoader | undefined;\n componentId: string;\n} {\n let _componentId = `/src/server/components/${componentReqId.toLowerCase()}`;\n let componentLoader: ComponentLoader | undefined = undefined;\n let componentId = _componentId;\n\n if (components[`${_componentId}.ts`]) {\n componentId = `${_componentId}.ts`;\n componentLoader = components[componentId] as ComponentLoader;\n }\n\n return { componentLoader, componentId };\n}\n\nfunction retrieveTransferredState(\n html: string,\n appId: string,\n): Record<string, unknown | undefined> {\n const regex = new RegExp(\n `<script id=\"${appId}-state\" type=\"application/json\">(.*?)<\\/script>`,\n );\n const match = html.match(regex);\n\n if (match) {\n const scriptContent = match[1];\n\n if (scriptContent) {\n try {\n const parsedContent: {\n _analog_output: Record<string, unknown | undefined>;\n } = JSON.parse(scriptContent);\n return parsedContent._analog_output || {};\n } catch (e) {\n console.warn('Exception while parsing static outputs for ' + appId, e);\n }\n }\n\n return {};\n } else {\n return {};\n }\n}\n","import {\n ApplicationConfig,\n Provider,\n Type,\n enableProdMode,\n} from '@angular/core';\nimport {\n bootstrapApplication,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport { renderApplication } from '@angular/platform-server';\nimport type { ServerContext } from '@analogjs/router/tokens';\n\nimport { provideServerContext } from './provide-server-context';\nimport {\n serverComponentRequest,\n renderServerComponent,\n} from './server-component-render';\n\nif (import.meta.env.PROD) {\n enableProdMode();\n}\n\n/**\n * Returns a function that accepts the navigation URL,\n * the root HTML, and server context.\n *\n * @param rootComponent\n * @param config\n * @param platformProviders\n * @returns Promise<string | Reponse>\n */\nexport function render(\n rootComponent: Type<unknown>,\n config: ApplicationConfig,\n platformProviders: Provider[] = [],\n) {\n function bootstrap(context?: BootstrapContext) {\n return bootstrapApplication(rootComponent, config, context);\n }\n\n return async function render(\n url: string,\n document: string,\n serverContext: ServerContext,\n ) {\n if (serverComponentRequest(serverContext)) {\n return await renderServerComponent(url, serverContext);\n }\n\n const html = await renderApplication(bootstrap, {\n document,\n url,\n platformProviders: [\n provideServerContext(serverContext),\n platformProviders,\n ],\n });\n\n return html;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["ɵresetCompiledComponents","SERVER_CONTEXT","Console"],"mappings":"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"}
1
+ {"version":3,"file":"analogjs-router-server.mjs","sources":["../../../../packages/router/server/src/provide-server-context.ts","../../../../packages/router/server/src/tokens.ts","../../../../packages/router/server/src/server-component-render.ts","../../../../packages/router/server/src/render.ts","../../../../packages/router/server/src/analogjs-router-server.ts"],"sourcesContent":["import { StaticProvider, ɵresetCompiledComponents } from '@angular/core';\nimport { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server';\n\nimport {\n BASE_URL,\n LOCALE,\n REQUEST,\n RESPONSE,\n ServerRequest,\n ServerResponse,\n} from '@analogjs/router/tokens';\n\nexport function provideServerContext({\n req,\n res,\n}: {\n req: ServerRequest;\n res: ServerResponse;\n}): StaticProvider[] {\n const baseUrl = getBaseUrl(req);\n const locale = detectLocale(req);\n\n if (import.meta.env.DEV) {\n ɵresetCompiledComponents();\n }\n\n return [\n { provide: SERVER_CONTEXT, useValue: 'ssr-analog' },\n { provide: REQUEST, useValue: req },\n { provide: RESPONSE, useValue: res },\n { provide: BASE_URL, useValue: baseUrl },\n ...(locale ? [{ provide: LOCALE, useValue: locale }] : []),\n ];\n}\n\n/**\n * Detects the locale from the request URL path prefix or Accept-Language header.\n * URL prefix takes priority (e.g. /fr/about -> 'fr').\n */\nexport function detectLocale(req: ServerRequest): string | undefined {\n const url = req.originalUrl || req.url || '';\n const localeFromUrl = extractLocaleFromUrl(url);\n if (localeFromUrl) {\n return localeFromUrl;\n }\n\n return parseAcceptLanguage(req.headers['accept-language']);\n}\n\n/**\n * Extracts a locale from the first URL path segment if it matches\n * a BCP 47-like pattern (e.g. 'en', 'en-US', 'zh-Hans-CN').\n */\nexport function extractLocaleFromUrl(url: string): string | undefined {\n const pathname = url.split('?')[0];\n const segments = pathname.split('/').filter(Boolean);\n if (segments.length === 0) {\n return undefined;\n }\n\n const firstSegment = segments[0];\n // Match BCP 47 language tags: 2-letter language code with optional region/script\n // e.g. 'en', 'en-US', 'zh-Hans', 'zh-Hans-CN'\n if (/^[a-z]{2}(-[a-zA-Z]{2,4})?(-[a-zA-Z]{2}|\\d{3})?$/.test(firstSegment)) {\n return firstSegment;\n }\n\n return undefined;\n}\n\n/**\n * Parses the Accept-Language header and returns the most preferred language.\n */\nexport function parseAcceptLanguage(\n header: string | undefined,\n): string | undefined {\n if (!header) {\n return undefined;\n }\n\n const locales = header\n .split(',')\n .map((part) => {\n const [locale, qPart] = part.trim().split(';');\n const q = qPart ? parseFloat(qPart.replace('q=', '')) : 1;\n return { locale: locale.trim(), q };\n })\n .sort((a, b) => b.q - a.q);\n\n return locales[0]?.locale || undefined;\n}\n\nexport function getBaseUrl(req: ServerRequest) {\n const protocol = getRequestProtocol(req);\n const { originalUrl, headers } = req;\n const parsedUrl = new URL(\n '',\n `${protocol}://${headers.host}${\n originalUrl.endsWith('/')\n ? originalUrl.substring(0, originalUrl.length - 1)\n : originalUrl\n }`,\n );\n const baseUrl = parsedUrl.origin;\n\n return baseUrl;\n}\n\nexport function getRequestProtocol(\n req: ServerRequest,\n opts: { xForwardedProto?: boolean } = {},\n) {\n if (\n opts.xForwardedProto !== false &&\n req.headers['x-forwarded-proto'] === 'https'\n ) {\n return 'https';\n }\n\n return (req.connection as any)?.encrypted ? 'https' : 'http';\n}\n","import {\n assertInInjectionContext,\n inject,\n InjectionToken,\n makeStateKey,\n Provider,\n TransferState,\n} from '@angular/core';\n\nexport const STATIC_PROPS = new InjectionToken<Record<string, any>>(\n 'Static Props',\n);\n\nexport function provideStaticProps<T = Record<string, any>>(\n props: T,\n): Provider {\n return {\n provide: STATIC_PROPS,\n useFactory() {\n return props;\n },\n };\n}\n\nexport function injectStaticProps() {\n assertInInjectionContext(injectStaticProps);\n\n return inject(STATIC_PROPS);\n}\n\nexport function injectStaticOutputs<T>() {\n const transferState = inject(TransferState);\n const outputsKey = makeStateKey<T>('_analog_output');\n\n return {\n set(data: T) {\n transferState.set(outputsKey, data);\n },\n };\n}\n","import { ApplicationConfig, Type } from '@angular/core';\nimport {\n bootstrapApplication,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport {\n reflectComponentType,\n ɵConsole as Console,\n APP_ID,\n} from '@angular/core';\nimport {\n provideServerRendering,\n renderApplication,\n ɵSERVER_CONTEXT as SERVER_CONTEXT,\n} from '@angular/platform-server';\nimport { ServerContext } from '@analogjs/router/tokens';\nimport { createEvent, readBody, getHeader } from 'h3';\n\nimport { provideStaticProps } from './tokens';\n\ntype ComponentLoader = () => Promise<Type<unknown>>;\n\nexport function serverComponentRequest(serverContext: ServerContext) {\n const serverComponentId = getHeader(\n createEvent(serverContext.req, serverContext.res),\n 'X-Analog-Component',\n );\n\n if (\n !serverComponentId &&\n serverContext.req.url &&\n serverContext.req.url.startsWith('/_analog/components')\n ) {\n const componentId = serverContext.req.url.split('/')?.[3];\n\n return componentId;\n }\n\n return serverComponentId;\n}\n\nconst components = import.meta.glob([\n '/src/server/components/**/*.{ts,analog,ag}',\n]);\n\nexport async function renderServerComponent(\n url: string,\n serverContext: ServerContext,\n config?: ApplicationConfig,\n) {\n const componentReqId = serverComponentRequest(serverContext) as string;\n const { componentLoader, componentId } = getComponentLoader(componentReqId);\n\n if (!componentLoader) {\n return new Response(`Server Component Not Found ${componentId}`, {\n status: 404,\n });\n }\n\n const component = ((await componentLoader()) as any)[\n 'default'\n ] as Type<unknown>;\n\n if (!component) {\n return new Response(`No default export for ${componentId}`, {\n status: 422,\n });\n }\n\n const mirror = reflectComponentType(component);\n const selector = mirror?.selector.split(',')?.[0] || 'server-component';\n const event = createEvent(serverContext.req, serverContext.res);\n const body = (await readBody(event)) || {};\n const appId = `analog-server-${selector.toLowerCase()}-${new Date().getTime()}`;\n\n const bootstrap = (context?: BootstrapContext) =>\n bootstrapApplication(\n component,\n {\n providers: [\n provideServerRendering(),\n provideStaticProps(body),\n { provide: SERVER_CONTEXT, useValue: 'analog-server-component' },\n {\n provide: APP_ID,\n useFactory() {\n return appId;\n },\n },\n ...(config?.providers || []),\n ],\n },\n context,\n );\n\n const html = await renderApplication(bootstrap, {\n url,\n document: `<${selector}></${selector}>`,\n platformProviders: [\n {\n provide: Console,\n useFactory() {\n return {\n warn: () => {},\n log: () => {},\n };\n },\n },\n ],\n });\n\n const outputs = retrieveTransferredState(html, appId);\n const responseData: { html: string; outputs: Record<string, any> } = {\n html,\n outputs,\n };\n\n return new Response(JSON.stringify(responseData), {\n headers: {\n 'X-Analog-Component': 'true',\n },\n });\n}\n\nfunction getComponentLoader(componentReqId: string): {\n componentLoader: ComponentLoader | undefined;\n componentId: string;\n} {\n let _componentId = `/src/server/components/${componentReqId.toLowerCase()}`;\n let componentLoader: ComponentLoader | undefined = undefined;\n let componentId = _componentId;\n\n if (components[`${_componentId}.ts`]) {\n componentId = `${_componentId}.ts`;\n componentLoader = components[componentId] as ComponentLoader;\n }\n\n return { componentLoader, componentId };\n}\n\nfunction retrieveTransferredState(\n html: string,\n appId: string,\n): Record<string, unknown | undefined> {\n const regex = new RegExp(\n `<script id=\"${appId}-state\" type=\"application/json\">(.*?)<\\/script>`,\n );\n const match = html.match(regex);\n\n if (match) {\n const scriptContent = match[1];\n\n if (scriptContent) {\n try {\n const parsedContent: {\n _analog_output: Record<string, unknown | undefined>;\n } = JSON.parse(scriptContent);\n return parsedContent._analog_output || {};\n } catch (e) {\n console.warn('Exception while parsing static outputs for ' + appId, e);\n }\n }\n\n return {};\n } else {\n return {};\n }\n}\n","import {\n ApplicationConfig,\n Provider,\n Type,\n enableProdMode,\n} from '@angular/core';\nimport {\n bootstrapApplication,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport { renderApplication } from '@angular/platform-server';\nimport type { ServerContext } from '@analogjs/router/tokens';\n\nimport { provideServerContext } from './provide-server-context';\nimport {\n serverComponentRequest,\n renderServerComponent,\n} from './server-component-render';\n\nif (import.meta.env.PROD) {\n enableProdMode();\n}\n\n/**\n * Nulls `def.tView` on every component definition that Angular has\n * compiled in this process. Angular caches the result of `consts()` on\n * `def.tView` — that factory is where `$localize` tagged templates are\n * evaluated — so without this reset the first rendered locale would be\n * frozen into the cache for the process lifetime.\n *\n * The set on `globalThis.__ngComponentDefs` is populated by a Vite\n * transform in `@analogjs/platform` that patches `@angular/core`'s\n * `getComponentId()` to mirror every compiled component definition to\n * a global Set, bypassing the `ngServerMode` guard that normally\n * prevents registration on the server.\n */\nfunction resetComponentDefTViews(): void {\n const defs = (globalThis as any).__ngComponentDefs as Set<any> | undefined;\n if (!defs) return;\n for (const def of defs) {\n def.tView = null;\n }\n}\n\n/**\n * Returns a function that accepts the navigation URL,\n * the root HTML, and server context.\n *\n * @param rootComponent\n * @param config\n * @param platformProviders\n * @returns Promise<string | Reponse>\n */\nexport function render(\n rootComponent: Type<unknown>,\n config: ApplicationConfig,\n platformProviders: Provider[] = [],\n) {\n function bootstrap(context?: BootstrapContext) {\n return bootstrapApplication(rootComponent, config, context);\n }\n\n return async function render(\n url: string,\n document: string,\n serverContext: ServerContext,\n ) {\n if (serverComponentRequest(serverContext)) {\n return await renderServerComponent(url, serverContext);\n }\n\n resetComponentDefTViews();\n\n const html = await renderApplication(bootstrap, {\n document,\n url,\n platformProviders: [\n provideServerContext(serverContext),\n platformProviders,\n ],\n });\n\n return html;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["ɵresetCompiledComponents","SERVER_CONTEXT","Console"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
- import { InjectionToken, inject } from '@angular/core';
1
+ import { InjectionToken, inject, assertInInjectionContext } from '@angular/core';
2
2
 
3
3
  const REQUEST = new InjectionToken('@analogjs/router Server Request');
4
4
  const RESPONSE = new InjectionToken('@analogjs/router Server Response');
5
5
  const BASE_URL = new InjectionToken('@analogjs/router Base URL');
6
6
  const API_PREFIX = new InjectionToken('@analogjs/router API Prefix');
7
+ const LOCALE = new InjectionToken('@analogjs/router Locale');
7
8
  function injectRequest() {
8
9
  return inject(REQUEST, { optional: true });
9
10
  }
@@ -16,10 +17,14 @@ function injectBaseURL() {
16
17
  function injectAPIPrefix() {
17
18
  return inject(API_PREFIX);
18
19
  }
20
+ function injectLocale() {
21
+ assertInInjectionContext(injectLocale);
22
+ return inject(LOCALE, { optional: true });
23
+ }
19
24
 
20
25
  /**
21
26
  * Generated bundle index. Do not edit.
22
27
  */
23
28
 
24
- export { API_PREFIX, BASE_URL, REQUEST, RESPONSE, injectAPIPrefix, injectBaseURL, injectRequest, injectResponse };
29
+ export { API_PREFIX, BASE_URL, LOCALE, REQUEST, RESPONSE, injectAPIPrefix, injectBaseURL, injectLocale, injectRequest, injectResponse };
25
30
  //# sourceMappingURL=analogjs-router-tokens.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-router-tokens.mjs","sources":["../../../../packages/router/tokens/src/index.ts","../../../../packages/router/tokens/src/analogjs-router-tokens.ts"],"sourcesContent":["import { InjectionToken, inject } from '@angular/core';\nimport type {\n IncomingMessage,\n ServerResponse as NodeServerResponse,\n} from 'node:http';\n\nexport type ServerRequest = IncomingMessage & { originalUrl: string };\nexport type ServerResponse = NodeServerResponse;\nexport type ServerContext = { req: ServerRequest; res: ServerResponse };\n\nexport const REQUEST = new InjectionToken<ServerRequest>(\n '@analogjs/router Server Request',\n);\nexport const RESPONSE = new InjectionToken<ServerResponse>(\n '@analogjs/router Server Response',\n);\nexport const BASE_URL = new InjectionToken<string>('@analogjs/router Base URL');\n\nexport const API_PREFIX = new InjectionToken<string>(\n '@analogjs/router API Prefix',\n);\n\nexport function injectRequest() {\n return inject(REQUEST, { optional: true });\n}\n\nexport function injectResponse() {\n return inject(RESPONSE, { optional: true });\n}\n\nexport function injectBaseURL() {\n return inject(BASE_URL, { optional: true });\n}\n\nexport function injectAPIPrefix() {\n return inject(API_PREFIX);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;MAUa,OAAO,GAAG,IAAI,cAAc,CACvC,iCAAiC;MAEtB,QAAQ,GAAG,IAAI,cAAc,CACxC,kCAAkC;MAEvB,QAAQ,GAAG,IAAI,cAAc,CAAS,2BAA2B;MAEjE,UAAU,GAAG,IAAI,cAAc,CAC1C,6BAA6B;SAGf,aAAa,GAAA;IAC3B,OAAO,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5C;SAEgB,cAAc,GAAA;IAC5B,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7C;SAEgB,aAAa,GAAA;IAC3B,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7C;SAEgB,eAAe,GAAA;AAC7B,IAAA,OAAO,MAAM,CAAC,UAAU,CAAC;AAC3B;;ACpCA;;AAEG;;;;"}
1
+ {"version":3,"file":"analogjs-router-tokens.mjs","sources":["../../../../packages/router/tokens/src/index.ts","../../../../packages/router/tokens/src/analogjs-router-tokens.ts"],"sourcesContent":["import {\n InjectionToken,\n assertInInjectionContext,\n inject,\n} from '@angular/core';\nimport type {\n IncomingMessage,\n ServerResponse as NodeServerResponse,\n} from 'node:http';\n\nexport type ServerRequest = IncomingMessage & { originalUrl: string };\nexport type ServerResponse = NodeServerResponse;\nexport type ServerContext = { req: ServerRequest; res: ServerResponse };\n\nexport const REQUEST = new InjectionToken<ServerRequest>(\n '@analogjs/router Server Request',\n);\nexport const RESPONSE = new InjectionToken<ServerResponse>(\n '@analogjs/router Server Response',\n);\nexport const BASE_URL = new InjectionToken<string>('@analogjs/router Base URL');\n\nexport const API_PREFIX = new InjectionToken<string>(\n '@analogjs/router API Prefix',\n);\n\nexport const LOCALE = new InjectionToken<string>('@analogjs/router Locale');\n\nexport function injectRequest() {\n return inject(REQUEST, { optional: true });\n}\n\nexport function injectResponse() {\n return inject(RESPONSE, { optional: true });\n}\n\nexport function injectBaseURL() {\n return inject(BASE_URL, { optional: true });\n}\n\nexport function injectAPIPrefix() {\n return inject(API_PREFIX);\n}\n\nexport function injectLocale() {\n assertInInjectionContext(injectLocale);\n return inject(LOCALE, { optional: true });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;MAca,OAAO,GAAG,IAAI,cAAc,CACvC,iCAAiC;MAEtB,QAAQ,GAAG,IAAI,cAAc,CACxC,kCAAkC;MAEvB,QAAQ,GAAG,IAAI,cAAc,CAAS,2BAA2B;MAEjE,UAAU,GAAG,IAAI,cAAc,CAC1C,6BAA6B;MAGlB,MAAM,GAAG,IAAI,cAAc,CAAS,yBAAyB;SAE1D,aAAa,GAAA;IAC3B,OAAO,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5C;SAEgB,cAAc,GAAA;IAC5B,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7C;SAEgB,aAAa,GAAA;IAC3B,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7C;SAEgB,eAAe,GAAA;AAC7B,IAAA,OAAO,MAAM,CAAC,UAAU,CAAC;AAC3B;SAEgB,YAAY,GAAA;IAC1B,wBAAwB,CAAC,YAAY,CAAC;IACtC,OAAO,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC3C;;AC/CA;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@analogjs/router",
3
- "version": "2.5.0-beta.5",
3
+ "version": "2.5.0-beta.50",
4
4
  "description": "Filesystem-based routing for Angular",
5
5
  "type": "module",
6
6
  "author": "Brandon Roberts <robertsbt@gmail.com>",
@@ -24,7 +24,7 @@
24
24
  "url": "https://github.com/sponsors/brandonroberts"
25
25
  },
26
26
  "peerDependencies": {
27
- "@analogjs/content": "^2.5.0-beta.5",
27
+ "@analogjs/content": "^2.5.0-beta.50",
28
28
  "@angular/core": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
29
29
  "@angular/router": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
30
30
  },
@@ -39,8 +39,7 @@
39
39
  "@analogjs/storybook-angular",
40
40
  "@analogjs/vite-plugin-angular",
41
41
  "@analogjs/vite-plugin-nitro",
42
- "@analogjs/vitest-angular",
43
- "@analogjs/angular-compiler"
42
+ "@analogjs/vitest-angular"
44
43
  ],
45
44
  "migrations": "./migrations/migration.json"
46
45
  },
@@ -58,6 +57,10 @@
58
57
  "types": "./types/analogjs-router.d.ts",
59
58
  "default": "./fesm2022/analogjs-router.mjs"
60
59
  },
60
+ "./i18n": {
61
+ "types": "./types/analogjs-router-i18n.d.ts",
62
+ "default": "./fesm2022/analogjs-router-i18n.mjs"
63
+ },
61
64
  "./server": {
62
65
  "types": "./types/analogjs-router-server.d.ts",
63
66
  "default": "./fesm2022/analogjs-router-server.mjs"
@@ -0,0 +1,82 @@
1
+ import { EnvironmentProviders } from '@angular/core';
2
+
3
+ /**
4
+ * Configuration for runtime i18n support.
5
+ *
6
+ * `defaultLocale` and `locales` are optional when the platform plugin
7
+ * is configured with `i18n` in `vite.config.ts` — the values are
8
+ * injected as build-time globals automatically.
9
+ */
10
+ interface I18nConfig {
11
+ /**
12
+ * The default locale to use when no locale is detected.
13
+ * If omitted, reads from the platform plugin's `i18n.defaultLocale`.
14
+ */
15
+ defaultLocale?: string;
16
+ /**
17
+ * List of supported locale identifiers.
18
+ * If omitted, reads from the platform plugin's `i18n.locales`.
19
+ */
20
+ locales?: string[];
21
+ /**
22
+ * A function that returns translations for a given locale.
23
+ * The returned record maps message IDs to translated strings.
24
+ */
25
+ loader: (locale: string) => Promise<Record<string, string>> | Record<string, string>;
26
+ }
27
+ /**
28
+ * Provides runtime i18n support using Angular's $localize.
29
+ *
30
+ * This provider:
31
+ * 1. Detects the active locale from the URL or falls back to the default.
32
+ * 2. Makes the current locale available via the LOCALE injection token.
33
+ * 3. Loads translations for the active locale at startup using $localize.
34
+ *
35
+ * Works in both SSR and client-only modes. On the client, locale is detected
36
+ * from `window.location.pathname`. On the server, locale is detected from
37
+ * the request in `provideServerContext()` and provided at the platform level;
38
+ * this function does not shadow it.
39
+ *
40
+ * When the platform plugin is configured with `i18n` in `vite.config.ts`,
41
+ * `defaultLocale` and `locales` are injected automatically — only
42
+ * `loader` is required:
43
+ *
44
+ * ```typescript
45
+ * provideI18n({
46
+ * loader: (locale) => import(`./i18n/${locale}.json`),
47
+ * })
48
+ * ```
49
+ */
50
+ declare function provideI18n(config: I18nConfig): EnvironmentProviders;
51
+ /**
52
+ * Loads translations into the global $localize translation map.
53
+ *
54
+ * Uses `@angular/localize`'s `loadTranslations` when available so that
55
+ * each translation string is parsed into the `{text, messageParts,
56
+ * placeholderNames}` shape that `$localize.translate` expects. Falls back
57
+ * to writing raw strings only as a last resort (in which case lookups
58
+ * will not succeed — the fallback exists to keep error messages useful
59
+ * for users who have not installed `@angular/localize`).
60
+ *
61
+ * Requires `@angular/localize/init` to be imported in the application
62
+ * entry point so that `globalThis.$localize` is defined.
63
+ */
64
+ declare function loadTranslationsRuntime(translations: Record<string, string>): Promise<void>;
65
+ /**
66
+ * Returns an injectable function that switches the application locale.
67
+ * Reads the configured locales from the I18N_CONFIG token provided
68
+ * by `provideI18n()`.
69
+ *
70
+ * Triggers a full page navigation to the new locale URL so that
71
+ * all $localize templates re-evaluate with the correct translations.
72
+ *
73
+ * Usage:
74
+ * ```typescript
75
+ * const switchLang = injectSwitchLocale();
76
+ * switchLang('fr'); // navigates to /fr/current-path
77
+ * ```
78
+ */
79
+ declare function injectSwitchLocale(): (targetLocale: string) => void;
80
+
81
+ export { injectSwitchLocale, loadTranslationsRuntime, provideI18n };
82
+ export type { I18nConfig };
@@ -13,10 +13,12 @@ declare const REQUEST: InjectionToken<ServerRequest>;
13
13
  declare const RESPONSE: InjectionToken<ServerResponse>;
14
14
  declare const BASE_URL: InjectionToken<string>;
15
15
  declare const API_PREFIX: InjectionToken<string>;
16
+ declare const LOCALE: InjectionToken<string>;
16
17
  declare function injectRequest(): ServerRequest | null;
17
18
  declare function injectResponse(): ServerResponse | null;
18
19
  declare function injectBaseURL(): string | null;
19
20
  declare function injectAPIPrefix(): string;
21
+ declare function injectLocale(): string | null;
20
22
 
21
- export { API_PREFIX, BASE_URL, REQUEST, RESPONSE, injectAPIPrefix, injectBaseURL, injectRequest, injectResponse };
23
+ export { API_PREFIX, BASE_URL, LOCALE, REQUEST, RESPONSE, injectAPIPrefix, injectBaseURL, injectLocale, injectRequest, injectResponse };
22
24
  export type { ServerContext, ServerRequest, ServerResponse };