@analogjs/router 2.5.0-beta.9 → 2.5.1-beta.1
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/fesm2022/analogjs-router-i18n.mjs +229 -0
- package/fesm2022/analogjs-router-i18n.mjs.map +1 -0
- package/fesm2022/analogjs-router-server.mjs +72 -1
- package/fesm2022/analogjs-router-server.mjs.map +1 -1
- package/fesm2022/analogjs-router-tokens.mjs +7 -2
- package/fesm2022/analogjs-router-tokens.mjs.map +1 -1
- package/package.json +7 -4
- package/types/analogjs-router-i18n.d.ts +82 -0
- package/types/analogjs-router-tokens.d.ts +3 -1
|
@@ -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 {
|
|
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.
|
|
3
|
+
"version": "2.5.1-beta.1",
|
|
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.
|
|
27
|
+
"@analogjs/content": "^2.5.1-beta.1",
|
|
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 };
|