@acms-subir/core 0.3.26 → 0.3.27

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @acms-subir/core
2
2
 
3
+ ## 0.3.27
4
+
5
+ ### Patch Changes
6
+
7
+ - 2972283: Redirects from another language now keep it. On a bilingual site, `/es/blog` went to `/sermon` (English) instead of `/es/sermon`, and the same happened to `/es/blog/<slug>`, a collection's `redirectFrom` bases, old `/blog/tag/…` addresses, and Admin → Redirects rules: a rule `/old` → `/new` sent `/es/old` to `/new`. A redirect on a `/es/…` address now adds the language back to its target, so these go to `/es/sermon`, `/es/tag/…` and `/es/new`. Full URLs (`https://…`) and targets that already start with a language are left as written. An admin rule written for the full `/es/old` address, which could never match before, now wins over the `/old` rule and is used exactly as written. The Redirects card says so on sites with more than one language.
8
+
3
9
  ## 0.3.26
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acms-subir/core",
3
- "version": "0.3.26",
3
+ "version": "0.3.27",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/middleware.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineMiddleware } from 'astro:middleware';
2
2
  import type { APIContext, MiddlewareNext } from 'astro';
3
- import { I18N } from './lib/i18n';
3
+ import { I18N, localePath, stripLocale } from './lib/i18n';
4
4
  import { bindAuthEnvironment, getRequestAuth, requestPerson, shouldResolveClerk, validateRequestSession, withClerkHeaders, type RequestAuth } from './lib/auth/request';
5
5
  import { roleHome } from './lib/roles';
6
6
  import { AUTH } from './lib/auth/config';
@@ -44,6 +44,15 @@ export function legacyContentRedirect(pathname: string): string | null {
44
44
  return null;
45
45
  }
46
46
 
47
+ // A /es/… request reaches the redirect lookup rewritten to its bare path (see
48
+ // handle), so a target on this site gets the visitor's language back: /blog →
49
+ // /sermon serves /es/blog → /es/sermon. Full URLs and targets that already
50
+ // name a language stay as written.
51
+ export function localizedRedirect(to: string, locale: string | undefined): string {
52
+ if (!locale || !to.startsWith('/') || to.startsWith('//') || stripLocale(to) !== to) return to;
53
+ return localePath(to, locale);
54
+ }
55
+
47
56
  // An admin form sent after the minute-long Clerk sign-in cookie lapsed (a page
48
57
  // whose script couldn't renew it in time): Clerk renews it only on a page GET,
49
58
  // so the POST arrives signed out and what it carried is gone. Back to the same
@@ -204,16 +213,22 @@ async function handle(context: APIContext, next: MiddlewareNext): Promise<Respon
204
213
 
205
214
  // Redirects fire only when the request would otherwise 404, so a rule can
206
215
  // never shadow real content. The table is cached per isolate for a minute.
216
+ // On a /es/… request a rule for the full address wins as written; otherwise
217
+ // the bare path's rule or legacy move applies in the visitor's language.
207
218
  if (response.status === 404 && !pathname.startsWith('/admin') && !pathname.startsWith('/api')) {
219
+ const { acmsLocale: locale, acmsPath } = context.locals as { acmsLocale?: string; acmsPath?: string };
208
220
  try {
209
- const hit = (await getRedirects(context.locals, now)).get(pathname);
210
- if (hit) return context.redirect(hit.toPath + context.url.search, hit.code);
221
+ const redirects = await getRedirects(context.locals, now);
222
+ const exact = acmsPath ? redirects.get(acmsPath) : undefined;
223
+ if (exact) return context.redirect(exact.toPath + context.url.search, exact.code);
224
+ const hit = redirects.get(pathname);
225
+ if (hit) return context.redirect(localizedRedirect(hit.toPath, locale) + context.url.search, hit.code);
211
226
  } catch {
212
227
  // a broken redirect lookup must never break the 404 itself
213
228
  }
214
229
  // admin-authored rules win; config-derived legacy moves are the fallback
215
230
  const legacy = legacyContentRedirect(pathname);
216
- if (legacy) return context.redirect(legacy + context.url.search, 301);
231
+ if (legacy) return context.redirect(localizedRedirect(legacy, locale) + context.url.search, 301);
217
232
  }
218
233
 
219
234
  return response;
@@ -8,6 +8,7 @@ import type { Permission } from '../../lib/permissions/catalog';
8
8
  import { AUTH } from '../../lib/auth/config';
9
9
  import { invalidateRedirectCache } from '../../middleware';
10
10
  import { CONTENT } from '../../lib/content';
11
+ import { extraLocales } from '../../lib/i18n';
11
12
  import { UMAMI_ID_RE, META_PIXEL_ID_RE, TIKTOK_PIXEL_ID_RE, normalizeScriptUrl } from '../../lib/analytics';
12
13
 
13
14
  const repo = await getRepo(Astro.locals);
@@ -200,6 +201,8 @@ if (Astro.request.method === 'POST') {
200
201
 
201
202
  const settings = await repo.getSettings();
202
203
  const redirects = await repo.listRedirects();
204
+ // the redirect card explains /<locale>/… rules with the site's first extra language
205
+ const extraLang = extraLocales()[0] ?? '';
203
206
  const backend = (Astro.locals as { runtime?: { env?: { DB?: unknown } } }).runtime?.env?.DB
204
207
  ? 'd1'
205
208
  : 'sqlite';
@@ -441,7 +444,7 @@ const groups = [
441
444
  <section class="scard" id="redirects">
442
445
  <header class="scard-head">
443
446
  <a class="scard-back" href="#" aria-label="All settings"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6" /></svg></a>
444
- <div class="grow"><h2>Redirects</h2><p>Forward old URLs to new ones. They only fire when a URL would otherwise 404, and renaming a published post adds one automatically.</p></div>
447
+ <div class="grow"><h2>Redirects</h2><p>Forward old URLs to new ones. They only fire when a URL would otherwise 404, and renaming a published post adds one automatically.{extraLang && ` A rule for /old-path also sends /${extraLang}/old-path to /${extraLang}/new-path; rules that start with /${extraLang}/ are used exactly as written.`}</p></div>
445
448
  </header>
446
449
  {redirects.length > 0 && (
447
450
  <div class="rd-list">