From 13d662cff6269d741da8cee0fba7d33934ef0aa6 Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:13:39 +0100 Subject: [PATCH] Add comments endpoint locale patch and translations --- README.md | 3 +- package.json | 4 +- scripts/patch-endpoint-comments-locales.mjs | 476 ++++++++++++++++++++ 3 files changed, 480 insertions(+), 3 deletions(-) create mode 100644 scripts/patch-endpoint-comments-locales.mjs diff --git a/README.md b/README.md index 30e754e4..5b3d6de3 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ WEBMENTION_SENDER_MOUNT_PATH=/webmention-sender # WEBMENTION_SENDER_ENDPOINT=http://127.0.0.1:3000/webmention-sender ``` -- Startup scripts run preflight + patch helpers before boot (`scripts/preflight-production-security.mjs`, `scripts/preflight-mongo-connection.mjs`, `scripts/preflight-activitypub-rsa-key.mjs`, `scripts/preflight-activitypub-profile-urls.mjs`, `scripts/patch-lightningcss.mjs`, `scripts/patch-endpoint-media-scope.mjs`, `scripts/patch-endpoint-media-sharp-runtime.mjs`, `scripts/patch-frontend-sharp-runtime.mjs`, `scripts/patch-endpoint-files-upload-route.mjs`, `scripts/patch-endpoint-files-upload-locales.mjs`, `scripts/patch-endpoint-activitypub-locales.mjs`, `scripts/patch-endpoint-activitypub-docloader-loglevel.mjs`, `scripts/patch-endpoint-activitypub-private-url-docloader.mjs`, `scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs`, `scripts/patch-endpoint-homepage-locales.mjs`, `scripts/patch-frontend-serviceworker-file.mjs`, `scripts/patch-conversations-collection-guards.mjs`, `scripts/patch-indiekit-routes-rate-limits.mjs`, `scripts/patch-indiekit-error-production-stack.mjs`, `scripts/patch-indieauth-devmode-guard.mjs`, `scripts/patch-listening-endpoint-runtime-guards.mjs`). +- Startup scripts run preflight + patch helpers before boot (`scripts/preflight-production-security.mjs`, `scripts/preflight-mongo-connection.mjs`, `scripts/preflight-activitypub-rsa-key.mjs`, `scripts/preflight-activitypub-profile-urls.mjs`, `scripts/patch-lightningcss.mjs`, `scripts/patch-endpoint-media-scope.mjs`, `scripts/patch-endpoint-media-sharp-runtime.mjs`, `scripts/patch-frontend-sharp-runtime.mjs`, `scripts/patch-endpoint-files-upload-route.mjs`, `scripts/patch-endpoint-files-upload-locales.mjs`, `scripts/patch-endpoint-activitypub-locales.mjs`, `scripts/patch-endpoint-activitypub-docloader-loglevel.mjs`, `scripts/patch-endpoint-activitypub-private-url-docloader.mjs`, `scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs`, `scripts/patch-endpoint-homepage-locales.mjs`, `scripts/patch-frontend-serviceworker-file.mjs`, `scripts/patch-endpoint-comments-locales.mjs`, `scripts/patch-conversations-collection-guards.mjs`, `scripts/patch-indiekit-routes-rate-limits.mjs`, `scripts/patch-indiekit-error-production-stack.mjs`, `scripts/patch-indieauth-devmode-guard.mjs`, `scripts/patch-listening-endpoint-runtime-guards.mjs`). - The production security preflight blocks startup on insecure auth/session configuration and catches empty-password bcrypt hashes. - One-time recovery mode is available with `INDIEKIT_ALLOW_PASSWORD_SETUP=1` to bootstrap/reset `PASSWORD_SECRET` when locked out. Remove this flag after setting a valid hash. - The media scope patch fixes a known upstream issue where file uploads can fail if the token scope is `create update delete` without explicit `media`. @@ -186,6 +186,7 @@ WEBMENTION_SENDER_MOUNT_PATH=/webmention-sender - The files upload route patch fixes browser multi-upload by posting to `/files/upload` (session-authenticated) instead of direct `/media` calls without bearer token. - The files upload locale patch adds missing `files.upload.dropText`/`files.upload.browse`/`files.upload.submitMultiple` labels in endpoint locale files so UI text does not render raw translation keys. - The ActivityPub locale patch backfills missing `de` locale keys from the endpoint's `en` locale and applies German admin title labels for notifications/profile. +- The comments locale patch backfills missing comments endpoint locale files, adds translations for de/es/fr/nl/pt/sv, and localizes dashboard labels that were hardcoded in the comments template. - The frontend serviceworker patch ensures `@indiekit/frontend/lib/serviceworker.js` exists at runtime, forces network-only handling for `/auth` and `/session` pages, patches frontend layout templates to unregister stale service workers and clear caches on load, and suppresses sidebar rendering whenever `app--minimalui` is present. - The conversations guard patch prevents `Cannot read properties of undefined (reading 'find')` when the `conversation_items` collection is temporarily unavailable. - The indiekit routes rate-limit patch (ported from `rmdes/indiekit-cloudron`) keeps strict limits on `/session/*`, applies generous limits to public API/well-known routes, and removes extra rate limiting from authenticated routes to avoid admin-side 429 spikes. diff --git a/package.json b/package.json index ba9e5db6..3c050b00 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "postinstall": "node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-private-url-docloader.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs", - "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-private-url-docloader.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", + "postinstall": "node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-private-url-docloader.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs", + "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-private-url-docloader.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/scripts/patch-endpoint-comments-locales.mjs b/scripts/patch-endpoint-comments-locales.mjs new file mode 100644 index 00000000..b997597c --- /dev/null +++ b/scripts/patch-endpoint-comments-locales.mjs @@ -0,0 +1,476 @@ +import { access, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; + +const endpointCandidates = [ + "node_modules/@rmdes/indiekit-endpoint-comments", + "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-comments", +]; + +const sourceLocale = "en"; +const targetLocales = [ + "de", + "es", + "fr", + "nl", + "pt", + "sv", + "es-419", + "pt-BR", + "hi", + "id", + "it", + "pl", + "sr", + "zh-Hans-CN", +]; + +const localeAliases = { + "es-419": "es", + "pt-BR": "pt", +}; + +const sourceOverrides = { + comments: { + dashboard: { + hiddenBadge: "Hidden", + targetPrefix: "on:", + paginationLabel: "Pagination", + previous: "Previous", + page: "Page", + of: "of", + next: "Next", + }, + }, +}; + +const localeOverrides = { + de: { + comments: { + title: "Kommentare", + dashboard: { + stats: "Statistiken", + totalComments: "Kommentare gesamt", + thisWeek: "Diese Woche", + uniqueCommenters: "Einzigartige Kommentierende", + hiddenComments: "Versteckte Kommentare", + commentList: "Kommentarliste", + filterAll: "Alle", + filterPublic: "Oeffentlich", + filterHidden: "Versteckt", + hide: "Verstecken", + purge: "Endgueltig loeschen", + purgeConfirm: "Sicher?", + restore: "Wiederherstellen", + noComments: "Noch keine Kommentare.", + recentActivity: "Letzte Aktivitaet", + commentPosted: "Kommentar veroeffentlicht", + commentHidden: "Kommentar versteckt", + commentPurged: "Kommentar geloescht", + commentRestored: "Kommentar wiederhergestellt", + hiddenBadge: "Versteckt", + targetPrefix: "zu:", + paginationLabel: "Seitennavigation", + previous: "Zurueck", + page: "Seite", + of: "von", + next: "Weiter", + }, + api: { + rateLimited: "Zu viele Kommentare. Bitte spaeter erneut versuchen.", + authRequired: "Bitte anmelden, um zu kommentieren.", + commentTooLong: "Kommentar ist zu lang.", + commentEmpty: "Kommentar darf nicht leer sein.", + posted: "Kommentar erfolgreich veroeffentlicht.", + }, + }, + }, + es: { + comments: { + title: "Comentarios", + dashboard: { + stats: "Estadisticas", + totalComments: "Comentarios totales", + thisWeek: "Esta semana", + uniqueCommenters: "Comentaristas unicos", + hiddenComments: "Comentarios ocultos", + commentList: "Lista de comentarios", + filterAll: "Todos", + filterPublic: "Publicos", + filterHidden: "Ocultos", + hide: "Ocultar", + purge: "Eliminar definitivamente", + purgeConfirm: "Seguro?", + restore: "Restaurar", + noComments: "Aun no hay comentarios.", + recentActivity: "Actividad reciente", + commentPosted: "Comentario publicado", + commentHidden: "Comentario ocultado", + commentPurged: "Comentario eliminado", + commentRestored: "Comentario restaurado", + hiddenBadge: "Oculto", + targetPrefix: "en:", + paginationLabel: "Paginacion", + previous: "Anterior", + page: "Pagina", + of: "de", + next: "Siguiente", + }, + api: { + rateLimited: "Demasiados comentarios. Intentalo mas tarde.", + authRequired: "Inicia sesion para comentar.", + commentTooLong: "El comentario supera la longitud maxima.", + commentEmpty: "El comentario no puede estar vacio.", + posted: "Comentario publicado correctamente.", + }, + }, + }, + fr: { + comments: { + title: "Commentaires", + dashboard: { + stats: "Statistiques", + totalComments: "Total des commentaires", + thisWeek: "Cette semaine", + uniqueCommenters: "Commentateurs uniques", + hiddenComments: "Commentaires masques", + commentList: "Liste des commentaires", + filterAll: "Tous", + filterPublic: "Publics", + filterHidden: "Masques", + hide: "Masquer", + purge: "Supprimer definitivement", + purgeConfirm: "Confirmer?", + restore: "Restaurer", + noComments: "Aucun commentaire pour le moment.", + recentActivity: "Activite recente", + commentPosted: "Commentaire publie", + commentHidden: "Commentaire masque", + commentPurged: "Commentaire supprime", + commentRestored: "Commentaire restaure", + hiddenBadge: "Masque", + targetPrefix: "sur:", + paginationLabel: "Pagination", + previous: "Precedent", + page: "Page", + of: "de", + next: "Suivant", + }, + api: { + rateLimited: "Trop de commentaires. Reessayez plus tard.", + authRequired: "Connectez-vous pour commenter.", + commentTooLong: "Le commentaire depasse la longueur maximale.", + commentEmpty: "Le commentaire ne peut pas etre vide.", + posted: "Commentaire publie avec succes.", + }, + }, + }, + nl: { + comments: { + title: "Reacties", + dashboard: { + stats: "Statistieken", + totalComments: "Totaal reacties", + thisWeek: "Deze week", + uniqueCommenters: "Unieke reageerders", + hiddenComments: "Verborgen reacties", + commentList: "Reactielijst", + filterAll: "Alles", + filterPublic: "Openbaar", + filterHidden: "Verborgen", + hide: "Verbergen", + purge: "Definitief verwijderen", + purgeConfirm: "Weet je het zeker?", + restore: "Herstellen", + noComments: "Nog geen reacties.", + recentActivity: "Recente activiteit", + commentPosted: "Reactie geplaatst", + commentHidden: "Reactie verborgen", + commentPurged: "Reactie verwijderd", + commentRestored: "Reactie hersteld", + hiddenBadge: "Verborgen", + targetPrefix: "op:", + paginationLabel: "Paginatie", + previous: "Vorige", + page: "Pagina", + of: "van", + next: "Volgende", + }, + api: { + rateLimited: "Te veel reacties. Probeer later opnieuw.", + authRequired: "Meld je aan om te reageren.", + commentTooLong: "Reactie is te lang.", + commentEmpty: "Reactie mag niet leeg zijn.", + posted: "Reactie succesvol geplaatst.", + }, + }, + }, + pt: { + comments: { + title: "Comentarios", + dashboard: { + stats: "Estatisticas", + totalComments: "Total de comentarios", + thisWeek: "Esta semana", + uniqueCommenters: "Comentadores unicos", + hiddenComments: "Comentarios ocultos", + commentList: "Lista de comentarios", + filterAll: "Todos", + filterPublic: "Publicos", + filterHidden: "Ocultos", + hide: "Ocultar", + purge: "Excluir permanentemente", + purgeConfirm: "Tem certeza?", + restore: "Restaurar", + noComments: "Ainda nao ha comentarios.", + recentActivity: "Atividade recente", + commentPosted: "Comentario publicado", + commentHidden: "Comentario ocultado", + commentPurged: "Comentario removido", + commentRestored: "Comentario restaurado", + hiddenBadge: "Oculto", + targetPrefix: "em:", + paginationLabel: "Paginacao", + previous: "Anterior", + page: "Pagina", + of: "de", + next: "Proximo", + }, + api: { + rateLimited: "Muitos comentarios. Tente novamente mais tarde.", + authRequired: "Faca login para comentar.", + commentTooLong: "O comentario excede o tamanho maximo.", + commentEmpty: "O comentario nao pode estar vazio.", + posted: "Comentario publicado com sucesso.", + }, + }, + }, + sv: { + comments: { + title: "Kommentarer", + dashboard: { + stats: "Statistik", + totalComments: "Totalt antal kommentarer", + thisWeek: "Denna vecka", + uniqueCommenters: "Unika kommentatorer", + hiddenComments: "Dolda kommentarer", + commentList: "Kommentarslista", + filterAll: "Alla", + filterPublic: "Offentliga", + filterHidden: "Dolda", + hide: "Dolj", + purge: "Radera permanent", + purgeConfirm: "Ar du saker?", + restore: "Aterstall", + noComments: "Inga kommentarer annu.", + recentActivity: "Senaste aktivitet", + commentPosted: "Kommentar publicerad", + commentHidden: "Kommentar dold", + commentPurged: "Kommentar raderad", + commentRestored: "Kommentar aterstalld", + hiddenBadge: "Dold", + targetPrefix: "pa:", + paginationLabel: "Sidindelning", + previous: "Foregaende", + page: "Sida", + of: "av", + next: "Nasta", + }, + api: { + rateLimited: "For manga kommentarer. Forsok igen senare.", + authRequired: "Logga in for att kommentera.", + commentTooLong: "Kommentaren overskrider maximal langd.", + commentEmpty: "Kommentaren kan inte vara tom.", + posted: "Kommentar publicerad.", + }, + }, + }, +}; + +const viewReplacements = [ + { + oldSnippet: 'Hidden', + newSnippet: + '{{ __("comments.dashboard.hiddenBadge") if __ else "Hidden" }}', + }, + { + oldSnippet: " on: {{ comment.target }}", + newSnippet: + ' {{ __("comments.dashboard.targetPrefix") if __ else "on:" }} {{ comment.target }}', + }, + { + oldSnippet: ` {% if totalPages > 1 %} + + {% endif %}`, + newSnippet: ` {% if totalPages > 1 %} + + {% endif %}`, + }, +]; + +function isObject(value) { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} + +function mergeMissing(target, fallback) { + if (target === undefined) { + return fallback; + } + + if (!isObject(target) || !isObject(fallback)) { + return target; + } + + const merged = { ...target }; + + for (const [key, fallbackValue] of Object.entries(fallback)) { + merged[key] = mergeMissing(merged[key], fallbackValue); + } + + return merged; +} + +function applyOverrides(target, overrides) { + if (!isObject(target) || !isObject(overrides)) { + return target; + } + + const merged = { ...target }; + + for (const [key, value] of Object.entries(overrides)) { + if (isObject(value)) { + const existing = isObject(merged[key]) ? merged[key] : {}; + merged[key] = applyOverrides(existing, value); + continue; + } + + merged[key] = value; + } + + return merged; +} + +async function exists(filePath) { + try { + await access(filePath); + return true; + } catch { + return false; + } +} + +let checkedEndpoints = 0; +let checkedLocales = 0; +let patchedLocales = 0; +let checkedTemplates = 0; +let patchedTemplates = 0; + +for (const endpointPath of endpointCandidates) { + if (!(await exists(endpointPath))) { + continue; + } + + checkedEndpoints += 1; + + const sourcePath = path.join(endpointPath, "locales", `${sourceLocale}.json`); + + if (!(await exists(sourcePath))) { + continue; + } + + let sourceLocaleJson; + try { + sourceLocaleJson = JSON.parse(await readFile(sourcePath, "utf8")); + } catch { + continue; + } + + const sourcePatched = applyOverrides(sourceLocaleJson, sourceOverrides); + checkedLocales += 1; + + if (JSON.stringify(sourcePatched) !== JSON.stringify(sourceLocaleJson)) { + await writeFile(sourcePath, `${JSON.stringify(sourcePatched, null, 2)}\n`, "utf8"); + patchedLocales += 1; + } + + for (const locale of targetLocales) { + const localePath = path.join(endpointPath, "locales", `${locale}.json`); + checkedLocales += 1; + + let localeJson = {}; + if (await exists(localePath)) { + try { + localeJson = JSON.parse(await readFile(localePath, "utf8")); + } catch { + localeJson = {}; + } + } + + const merged = mergeMissing(localeJson, sourcePatched); + const overrideKey = localeAliases[locale] || locale; + const patched = applyOverrides(merged, localeOverrides[overrideKey] || {}); + + if (JSON.stringify(patched) === JSON.stringify(localeJson)) { + continue; + } + + await writeFile(localePath, `${JSON.stringify(patched, null, 2)}\n`, "utf8"); + patchedLocales += 1; + } + + const viewPath = path.join(endpointPath, "views", "comments.njk"); + if (!(await exists(viewPath))) { + continue; + } + + checkedTemplates += 1; + + const viewSource = await readFile(viewPath, "utf8"); + let viewUpdated = viewSource; + let templateChanged = false; + + for (const replacement of viewReplacements) { + if (viewUpdated.includes(replacement.newSnippet)) { + continue; + } + + if (!viewUpdated.includes(replacement.oldSnippet)) { + continue; + } + + viewUpdated = viewUpdated.replace(replacement.oldSnippet, replacement.newSnippet); + templateChanged = true; + } + + if (!templateChanged) { + continue; + } + + await writeFile(viewPath, viewUpdated, "utf8"); + patchedTemplates += 1; +} + +if (checkedEndpoints === 0) { + console.log("[postinstall] No comments endpoint directories found"); +} else if (patchedLocales === 0 && patchedTemplates === 0) { + console.log("[postinstall] comments locales and templates already patched"); +} else { + console.log( + `[postinstall] Patched comments locales in ${patchedLocales}/${checkedLocales} file(s) and templates in ${patchedTemplates}/${checkedTemplates} file(s)`, + ); +}