euparliamentmonitor 0.9.27 → 0.9.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { BASE_URL, BUILD_SHORT, MERMAID_VERSION } from '../../constants/config.js';
14
14
  import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
15
- import { ALL_LANGUAGES, PAGE_TITLES, SKIP_LINK_TEXTS, ARTICLE_NAV_LABELS, BACK_TO_NEWS_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
15
+ import { ALL_LANGUAGES, PAGE_TITLES, SKIP_LINK_TEXTS, ARTICLE_NAV_LABELS, BACK_TO_NEWS_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
16
16
  import { buildOgLocaleTags } from '../../constants/og-locales.js';
17
17
  import { ORG_SAME_AS, buildTwitterAttributionTags } from '../../constants/social-handles.js';
18
18
  import { escapeHTML } from '../../utils/file-utils.js';
@@ -186,7 +186,7 @@ export function wrapArticleHtml(options) {
186
186
  const tocHtml = buildArticleToc(options.toc ?? [], safeLang);
187
187
  const articleMainClass = tocHtml.length > 0 ? 'article-main--with-toc' : 'article-main--no-toc';
188
188
  const articleSectionLabel = getLocalizedArticleTypePlain(options.articleType, safeLang);
189
- const disclosureBody = buildProgressiveDisclosureBody(options.body);
189
+ const disclosureBody = buildProgressiveDisclosureBody(options.body, safeLang);
190
190
  const transformedBodyHtml = options.readerFriendly === false
191
191
  ? disclosureBody.bodyHtml
192
192
  : applyReaderFriendlyTransform(disclosureBody.bodyHtml);
@@ -199,7 +199,9 @@ export function wrapArticleHtml(options) {
199
199
  disclosureBody.wordCounts.analysis +
200
200
  disclosureBody.wordCounts.intelligence;
201
201
  const readingTimes = options.readingTimes ?? buildLayerReadingTimes(disclosureBody.wordCounts);
202
- const readingTimeLine = `⏱️ Quick read: ${readingTimes.quickRead} min · Full analysis: ${readingTimes.fullAnalysis} min · Complete intelligence: ${readingTimes.completeIntelligence} min`;
202
+ const disclosureLabels = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, safeLang);
203
+ const min = disclosureLabels.minutesAbbr;
204
+ const readingTimeLine = `⏱️ ${disclosureLabels.quickRead}: ${readingTimes.quickRead}${min} · ${disclosureLabels.fullAnalysis}: ${readingTimes.fullAnalysis}${min} · ${disclosureLabels.completeIntelligence}: ${readingTimes.completeIntelligence}${min}`;
203
205
  // Pre-compute the per-surface SEO-budget-clamped variants of title
204
206
  // and description. Each surface gets its own clamp tuned to the
205
207
  // documented platform envelope (Google/Bing SERP, Facebook/LinkedIn
@@ -392,7 +394,7 @@ ${tocHtml} <article class="article-body" lang="${safeLang}">
392
394
  <p class="article-kicker">${escapeHTML(getLocalizedArticleType(options.articleType, safeLang))}</p>
393
395
  <h1>${escapeHTML(options.title)}</h1>
394
396
  <p class="article-dek">${escapeHTML(options.description)}</p>
395
- <p class="article-reading-times" aria-label="Estimated reading time">${escapeHTML(readingTimeLine)}</p>
397
+ <p class="article-reading-times" aria-label="${escapeHTML(disclosureLabels.readingTimeAria)}">${escapeHTML(readingTimeLine)}</p>
396
398
  <p class="article-meta"><time datetime="${options.date}">${options.date}</time> · EU Parliament Monitor</p>
397
399
  </header>
398
400
  ${sourceMdLink}
@@ -8,7 +8,7 @@
8
8
  * that mirrors the Reader Intelligence Guide so the two navigation
9
9
  * surfaces share a single visual vocabulary.
10
10
  */
11
- import { TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, ANALYSIS_INDEX_HEADING_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, getLocalizedString, } from '../../constants/languages.js';
11
+ import { TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, ANALYSIS_INDEX_HEADING_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, getLocalizedString, } from '../../constants/languages.js';
12
12
  import { escapeHTML } from '../../utils/file-utils.js';
13
13
  import { READER_GUIDE_SECTION_ID } from '../reader-guide-constants.js';
14
14
  import { READER_GUIDE_TITLE_LABELS, getReaderGuideSectionIcon, } from '../reader-intelligence-guide.js';
@@ -91,13 +91,15 @@ export function buildArticleToc(entries, lang) {
91
91
  if (entries.length === 0)
92
92
  return '';
93
93
  const label = escapeHTML(getLocalizedString(TOC_ARIA_LABELS, lang));
94
+ const layerBadgeWord = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, lang).layerBadge;
94
95
  const items = entries
95
96
  .map((e) => {
96
97
  const displayTitle = getLocalizedTocTitle(e.id, e.title, lang);
97
98
  const icon = getTocSectionIcon(e.id);
98
99
  const layer = resolveDisclosureLayer(e.id);
99
100
  const layerBadge = layer === 'quick' ? 'L1' : layer === 'analysis' ? 'L2' : 'L3';
100
- return ` <li data-layer="${layer}"><a href="#${escapeHTML(e.id)}"><span class="article-toc-icon" aria-hidden="true">${icon}</span> <span class="article-toc-text">${escapeHTML(displayTitle)}</span><span class="article-toc-layer article-toc-layer--${layer}" aria-label="Layer ${layerBadge}">${layerBadge}</span></a></li>`;
101
+ const layerAria = escapeHTML(`${layerBadgeWord} ${layerBadge}`);
102
+ return ` <li data-layer="${layer}"><a href="#${escapeHTML(e.id)}"><span class="article-toc-icon" aria-hidden="true">${icon}</span> <span class="article-toc-text">${escapeHTML(displayTitle)}</span><span class="article-toc-layer article-toc-layer--${layer}" aria-label="${layerAria}">${layerBadge}</span></a></li>`;
101
103
  })
102
104
  .join('\n');
103
105
  return [
@@ -1,3 +1,4 @@
1
+ import type { LanguageCode } from '../types/index.js';
1
2
  export type DisclosureLayer = 'quick' | 'analysis' | 'intelligence';
2
3
  export interface LayerWordCounts {
3
4
  readonly quick: number;
@@ -20,7 +21,7 @@ export declare function splitBodyIntoDisclosureLayers(bodyHtml: string): {
20
21
  };
21
22
  export declare function estimateReadingMinutes(wordCount: number): number;
22
23
  export declare function buildLayerReadingTimes(words: LayerWordCounts): LayerReadingTimes;
23
- export declare function buildProgressiveDisclosureBody(bodyHtml: string): {
24
+ export declare function buildProgressiveDisclosureBody(bodyHtml: string, lang?: LanguageCode): {
24
25
  readonly bodyHtml: string;
25
26
  readonly wordCounts: LayerWordCounts;
26
27
  };
@@ -1,6 +1,9 @@
1
1
  // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { stripHtmlTags } from '../utils/html-sanitize.js';
4
+ import { escapeHTML } from '../utils/file-utils.js';
5
+ import { PROGRESSIVE_DISCLOSURE_LABELS } from '../constants/languages.js';
6
+ import { getLocalizedString } from '../constants/language-core.js';
4
7
  export const QUICK_LAYER_SECTION_IDS = new Set([
5
8
  'executive-brief',
6
9
  'key-takeaways',
@@ -87,18 +90,19 @@ export function buildLayerReadingTimes(words) {
87
90
  completeIntelligence: estimateReadingMinutes(complete),
88
91
  };
89
92
  }
90
- export function buildProgressiveDisclosureBody(bodyHtml) {
93
+ export function buildProgressiveDisclosureBody(bodyHtml, lang = 'en') {
94
+ const labels = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, lang);
91
95
  const layers = splitBodyIntoDisclosureLayers(bodyHtml);
92
96
  const output = [
93
- `<section class="article-layer article-layer--quick" data-disclosure-layer="quick" aria-label="Quick read">`,
97
+ `<section class="article-layer article-layer--quick" data-disclosure-layer="quick" aria-label="${escapeHTML(labels.quickRead)}">`,
94
98
  layers.quickHtml,
95
99
  `</section>`,
96
100
  ];
97
101
  if (layers.analysisHtml.trim().length > 0) {
98
- output.push(`<details class="article-layer article-layer--analysis article-layer-details" data-disclosure-layer="analysis" id="article-layer-analysis">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">Read full analysis ↓</span></summary>`, `<section class="article-layer-content" aria-label="Full analysis">`, layers.analysisHtml, `</section>`, `</details>`);
102
+ output.push(`<details class="article-layer article-layer--analysis article-layer-details" data-disclosure-layer="analysis" id="article-layer-analysis">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">${escapeHTML(labels.expandAnalysis)} ↓</span></summary>`, `<section class="article-layer-content" aria-label="${escapeHTML(labels.fullAnalysis)}">`, layers.analysisHtml, `</section>`, `</details>`);
99
103
  }
100
104
  if (layers.intelligenceHtml.trim().length > 0) {
101
- output.push(`<details class="article-layer article-layer--intelligence article-layer-details" data-disclosure-layer="intelligence" id="article-layer-intelligence">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">Open complete intelligence ↓</span></summary>`, `<section class="article-layer-content" aria-label="Complete intelligence">`, layers.intelligenceHtml, `</section>`, `</details>`);
105
+ output.push(`<details class="article-layer article-layer--intelligence article-layer-details" data-disclosure-layer="intelligence" id="article-layer-intelligence">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">${escapeHTML(labels.expandIntelligence)} ↓</span></summary>`, `<section class="article-layer-content" aria-label="${escapeHTML(labels.completeIntelligence)}">`, layers.intelligenceHtml, `</section>`, `</details>`);
102
106
  }
103
107
  return { bodyHtml: output.join('\n'), wordCounts: layers.wordCounts };
104
108
  }
@@ -8,7 +8,8 @@
8
8
  * - **language-articles** — Article-type title generators and body-text strings
9
9
  */
10
10
  export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
11
- export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
11
+ export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
12
12
  export type { AISection, RelationshipLabels, RelatedAnalysisStrings } from './language-ui.js';
13
+ export type { ProgressiveDisclosureStrings } from './language-ui.js';
13
14
  export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
14
15
  //# sourceMappingURL=languages.d.ts.map
@@ -10,6 +10,6 @@
10
10
  * - **language-articles** — Article-type title generators and body-text strings
11
11
  */
12
12
  export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
13
- export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
13
+ export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
14
14
  export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
15
15
  //# sourceMappingURL=languages.js.map
@@ -11,6 +11,7 @@
11
11
  * - `article-category-labels.ts` — `ARTICLE_TYPE_LABELS`, `ARTICLE_TYPE_ICONS`
12
12
  * - `accessibility.ts` — `SKIP_LINK_TEXTS`, `TOC_ARIA_LABELS`, language-switcher / footer trust-badge ARIA labels
13
13
  * - `reading-time.ts` — `READ_TIME_LABELS` (per-language pluralization)
14
+ * - `progressive-disclosure.ts` — `PROGRESSIVE_DISCLOSURE_LABELS` (reading layers, expand CTAs, reading-time line, TOC layer badge)
14
15
  * - `ai-content.ts` — `AI_SECTION_CONTENT` + `AISection` interface
15
16
  * - `related-analysis.ts` — `SECTION_TITLE_LABELS`, `RELATED_ANALYSIS_LABELS`
16
17
  * - `tradecraft-cards.ts` — tradecraft / analysis-index card labels
@@ -24,6 +25,7 @@ export { FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LIN
24
25
  export { ARTICLE_TYPE_LABELS, ARTICLE_TYPE_ICONS, HE_DEEP_ANALYSIS, } from './article-category-labels.js';
25
26
  export { SKIP_LINK_TEXTS, TOC_ARIA_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './accessibility.js';
26
27
  export { READ_TIME_LABELS } from './reading-time.js';
28
+ export { PROGRESSIVE_DISCLOSURE_LABELS, type ProgressiveDisclosureStrings, } from './progressive-disclosure.js';
27
29
  export { AI_SECTION_CONTENT, type AISection } from './ai-content.js';
28
30
  export { SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, type RelationshipLabels, type RelatedAnalysisStrings, } from './related-analysis.js';
29
31
  export { TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, OPEN_SOURCE_NOTE_LABELS, } from './tradecraft-cards.js';
@@ -13,6 +13,7 @@
13
13
  * - `article-category-labels.ts` — `ARTICLE_TYPE_LABELS`, `ARTICLE_TYPE_ICONS`
14
14
  * - `accessibility.ts` — `SKIP_LINK_TEXTS`, `TOC_ARIA_LABELS`, language-switcher / footer trust-badge ARIA labels
15
15
  * - `reading-time.ts` — `READ_TIME_LABELS` (per-language pluralization)
16
+ * - `progressive-disclosure.ts` — `PROGRESSIVE_DISCLOSURE_LABELS` (reading layers, expand CTAs, reading-time line, TOC layer badge)
16
17
  * - `ai-content.ts` — `AI_SECTION_CONTENT` + `AISection` interface
17
18
  * - `related-analysis.ts` — `SECTION_TITLE_LABELS`, `RELATED_ANALYSIS_LABELS`
18
19
  * - `tradecraft-cards.ts` — tradecraft / analysis-index card labels
@@ -26,6 +27,7 @@ export { FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LIN
26
27
  export { ARTICLE_TYPE_LABELS, ARTICLE_TYPE_ICONS, HE_DEEP_ANALYSIS, } from './article-category-labels.js';
27
28
  export { SKIP_LINK_TEXTS, TOC_ARIA_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './accessibility.js';
28
29
  export { READ_TIME_LABELS } from './reading-time.js';
30
+ export { PROGRESSIVE_DISCLOSURE_LABELS, } from './progressive-disclosure.js';
29
31
  export { AI_SECTION_CONTENT } from './ai-content.js';
30
32
  export { SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, } from './related-analysis.js';
31
33
  export { TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, OPEN_SOURCE_NOTE_LABELS, } from './tradecraft-cards.js';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @module Constants/UI/ProgressiveDisclosure
3
+ * @description Localized chrome for the article progressive-disclosure
4
+ * surface — the three reading layers (quick / full analysis / complete
5
+ * intelligence), the visible `<summary>` expand CTAs, the reading-time
6
+ * estimate line, and the Table-of-Contents layer badge.
7
+ *
8
+ * A single `PROGRESSIVE_DISCLOSURE_LABELS` map is shared by every call
9
+ * site (the layer wrappers in `aggregator/progressive-disclosure.ts`, the
10
+ * reading-time line in `aggregator/html/shell.ts`, and the TOC badge in
11
+ * `aggregator/html/toc.ts`) so the layer vocabulary stays consistent and
12
+ * each phrase is translated exactly once.
13
+ */
14
+ import type { LanguageMap } from '../../types/index.js';
15
+ /** Per-language UI strings for the article progressive-disclosure chrome. */
16
+ export interface ProgressiveDisclosureStrings {
17
+ /** Name of the always-visible quick-read layer (reading-time + aria). */
18
+ readonly quickRead: string;
19
+ /** Name of the cumulative full-analysis layer (reading-time + aria). */
20
+ readonly fullAnalysis: string;
21
+ /** Name of the cumulative complete-intelligence layer (reading-time + aria). */
22
+ readonly completeIntelligence: string;
23
+ /** Visible CTA on the analysis `<summary>` toggle (a `↓` glyph is appended in markup). */
24
+ readonly expandAnalysis: string;
25
+ /** Visible CTA on the intelligence `<summary>` toggle (a `↓` glyph is appended in markup). */
26
+ readonly expandIntelligence: string;
27
+ /** Abbreviation for minutes used in the reading-time line (includes leading space for non-CJK locales). */
28
+ readonly minutesAbbr: string;
29
+ /** Aria-label for the reading-time estimate paragraph. */
30
+ readonly readingTimeAria: string;
31
+ /** Aria-label prefix for the TOC layer badge (rendered as e.g. "Layer L1"). */
32
+ readonly layerBadge: string;
33
+ }
34
+ /**
35
+ * Localised progressive-disclosure strings across all 14 supported
36
+ * languages. Reused by the layer wrappers, the reading-time line, and the
37
+ * TOC layer badge so the layer vocabulary is translated only once.
38
+ */
39
+ export declare const PROGRESSIVE_DISCLOSURE_LABELS: LanguageMap<ProgressiveDisclosureStrings>;
40
+ //# sourceMappingURL=progressive-disclosure.d.ts.map
@@ -0,0 +1,150 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Localised progressive-disclosure strings across all 14 supported
5
+ * languages. Reused by the layer wrappers, the reading-time line, and the
6
+ * TOC layer badge so the layer vocabulary is translated only once.
7
+ */
8
+ export const PROGRESSIVE_DISCLOSURE_LABELS = {
9
+ en: {
10
+ quickRead: 'Quick read',
11
+ fullAnalysis: 'Full analysis',
12
+ completeIntelligence: 'Complete intelligence',
13
+ expandAnalysis: 'Read full analysis',
14
+ expandIntelligence: 'Open complete intelligence',
15
+ minutesAbbr: ' min',
16
+ readingTimeAria: 'Estimated reading time',
17
+ layerBadge: 'Layer',
18
+ },
19
+ sv: {
20
+ quickRead: 'Snabbläsning',
21
+ fullAnalysis: 'Fullständig analys',
22
+ completeIntelligence: 'Komplett underrättelse',
23
+ expandAnalysis: 'Läs fullständig analys',
24
+ expandIntelligence: 'Öppna komplett underrättelse',
25
+ minutesAbbr: ' min',
26
+ readingTimeAria: 'Uppskattad lästid',
27
+ layerBadge: 'Nivå',
28
+ },
29
+ da: {
30
+ quickRead: 'Hurtig læsning',
31
+ fullAnalysis: 'Fuld analyse',
32
+ completeIntelligence: 'Komplet efterretning',
33
+ expandAnalysis: 'Læs fuld analyse',
34
+ expandIntelligence: 'Åbn komplet efterretning',
35
+ minutesAbbr: ' min',
36
+ readingTimeAria: 'Anslået læsetid',
37
+ layerBadge: 'Niveau',
38
+ },
39
+ no: {
40
+ quickRead: 'Hurtiglesing',
41
+ fullAnalysis: 'Full analyse',
42
+ completeIntelligence: 'Komplett etterretning',
43
+ expandAnalysis: 'Les full analyse',
44
+ expandIntelligence: 'Åpne komplett etterretning',
45
+ minutesAbbr: ' min',
46
+ readingTimeAria: 'Anslått lesetid',
47
+ layerBadge: 'Nivå',
48
+ },
49
+ fi: {
50
+ quickRead: 'Pikaluku',
51
+ fullAnalysis: 'Täysi analyysi',
52
+ completeIntelligence: 'Täydellinen tiedustelu',
53
+ expandAnalysis: 'Lue täysi analyysi',
54
+ expandIntelligence: 'Avaa täydellinen tiedustelu',
55
+ minutesAbbr: ' min',
56
+ readingTimeAria: 'Arvioitu lukuaika',
57
+ layerBadge: 'Taso',
58
+ },
59
+ de: {
60
+ quickRead: 'Schnelllektüre',
61
+ fullAnalysis: 'Vollständige Analyse',
62
+ completeIntelligence: 'Vollständige Aufklärung',
63
+ expandAnalysis: 'Vollständige Analyse lesen',
64
+ expandIntelligence: 'Vollständige Aufklärung öffnen',
65
+ minutesAbbr: ' Min.',
66
+ readingTimeAria: 'Geschätzte Lesezeit',
67
+ layerBadge: 'Ebene',
68
+ },
69
+ fr: {
70
+ quickRead: 'Lecture rapide',
71
+ fullAnalysis: 'Analyse complète',
72
+ completeIntelligence: 'Renseignement complet',
73
+ expandAnalysis: "Lire l'analyse complète",
74
+ expandIntelligence: 'Ouvrir le renseignement complet',
75
+ minutesAbbr: ' min',
76
+ readingTimeAria: 'Temps de lecture estimé',
77
+ layerBadge: 'Niveau',
78
+ },
79
+ es: {
80
+ quickRead: 'Lectura rápida',
81
+ fullAnalysis: 'Análisis completo',
82
+ completeIntelligence: 'Inteligencia completa',
83
+ expandAnalysis: 'Leer análisis completo',
84
+ expandIntelligence: 'Abrir inteligencia completa',
85
+ minutesAbbr: ' min',
86
+ readingTimeAria: 'Tiempo de lectura estimado',
87
+ layerBadge: 'Nivel',
88
+ },
89
+ nl: {
90
+ quickRead: 'Snel lezen',
91
+ fullAnalysis: 'Volledige analyse',
92
+ completeIntelligence: 'Volledige inlichtingen',
93
+ expandAnalysis: 'Lees volledige analyse',
94
+ expandIntelligence: 'Volledige inlichtingen openen',
95
+ minutesAbbr: ' min',
96
+ readingTimeAria: 'Geschatte leestijd',
97
+ layerBadge: 'Niveau',
98
+ },
99
+ ar: {
100
+ quickRead: 'قراءة سريعة',
101
+ fullAnalysis: 'تحليل كامل',
102
+ completeIntelligence: 'استخبارات كاملة',
103
+ expandAnalysis: 'اقرأ التحليل الكامل',
104
+ expandIntelligence: 'افتح الاستخبارات الكاملة',
105
+ minutesAbbr: ' دقيقة',
106
+ readingTimeAria: 'وقت القراءة المقدر',
107
+ layerBadge: 'المستوى',
108
+ },
109
+ he: {
110
+ quickRead: 'קריאה מהירה',
111
+ fullAnalysis: 'ניתוח מלא',
112
+ completeIntelligence: 'מודיעין מלא',
113
+ expandAnalysis: 'קראו את הניתוח המלא',
114
+ expandIntelligence: 'פתחו מודיעין מלא',
115
+ minutesAbbr: ' דק׳',
116
+ readingTimeAria: 'זמן קריאה משוער',
117
+ layerBadge: 'שכבה',
118
+ },
119
+ ja: {
120
+ quickRead: 'クイックリード',
121
+ fullAnalysis: '完全な分析',
122
+ completeIntelligence: '完全なインテリジェンス',
123
+ expandAnalysis: '完全な分析を読む',
124
+ expandIntelligence: '完全なインテリジェンスを開く',
125
+ minutesAbbr: '分',
126
+ readingTimeAria: '推定読了時間',
127
+ layerBadge: 'レイヤー',
128
+ },
129
+ ko: {
130
+ quickRead: '빠른 읽기',
131
+ fullAnalysis: '전체 분석',
132
+ completeIntelligence: '완전한 인텔리전스',
133
+ expandAnalysis: '전체 분석 읽기',
134
+ expandIntelligence: '완전한 인텔리전스 열기',
135
+ minutesAbbr: '분',
136
+ readingTimeAria: '예상 읽기 시간',
137
+ layerBadge: '레이어',
138
+ },
139
+ zh: {
140
+ quickRead: '快速阅读',
141
+ fullAnalysis: '完整分析',
142
+ completeIntelligence: '完整情报',
143
+ expandAnalysis: '阅读完整分析',
144
+ expandIntelligence: '打开完整情报',
145
+ minutesAbbr: '分钟',
146
+ readingTimeAria: '预计阅读时间',
147
+ layerBadge: '层级',
148
+ },
149
+ };
150
+ //# sourceMappingURL=progressive-disclosure.js.map
@@ -95,6 +95,15 @@
95
95
  * { "lang": "zh", "count": 92 }
96
96
  * ]
97
97
  * },
98
+ * "translationRegister": {
99
+ * "sv": {
100
+ * "name": "Swedish", "family": "Nordic",
101
+ * "register": "Formal, impersonal …",
102
+ * "fixedTokenNote": "IMF/WEO verbatim — never IVF …",
103
+ * "terms": { "European Parliament": "Europaparlamentet", … }
104
+ * },
105
+ * ...
106
+ * },
98
107
  * "queue": [
99
108
  * {
100
109
  * "date": "2026-05-15",
@@ -426,6 +435,276 @@ const FIXED_TOKEN_CLASSES = Object.freeze([
426
435
  { key: 'procedure-id', pattern: /\b\d{4}\/\d{4}\([A-Z]{3}\)/g },
427
436
  ]);
428
437
 
438
+ /**
439
+ * Machine-readable mirror of the per-language register the translator agent
440
+ * needs on every run — the style register (§ 4) and canonical EP terminology
441
+ * pairs (§ 5) of `analysis/methodologies/executive-brief-translation-guide.md`.
442
+ *
443
+ * Emitted ONCE into the discovery queue payload (as `translationRegister`,
444
+ * filtered to the languages the run actually touches) so the agent reads the
445
+ * rows it needs inline from `queue.json` instead of opening the full ~18 KB
446
+ * guide in-session on every turn. Bounding what enters the agent's context
447
+ * directly bounds the per-session effective-token accumulation that is the
448
+ * binding constraint on this workflow (see news-translate.md frontmatter and
449
+ * regression run #26641760920).
450
+ *
451
+ * Each entry:
452
+ * - `name` — endonym/English name of the language (operator-facing)
453
+ * - `family` — register family (Nordic | EU-core | RTL | CJK)
454
+ * - `register` — one-line style-register reminder (guide § 4)
455
+ * - `fixedTokenNote` — one-line FIXED-TOKEN preservation trap (guide § 4,
456
+ * validator gate #5); the highest-frequency failure mode
457
+ * - `terms` — canonical EN→target EP terminology pairs (guide § 5)
458
+ *
459
+ * This is a deliberate, reviewed duplication of the prose guide: when § 4 / § 5
460
+ * change, update this constant in the same PR. The drift-guard test
461
+ * `discover-untranslated-briefs.test.js` asserts the shape and a few anchor
462
+ * values so the two cannot silently diverge on the fields that matter.
463
+ *
464
+ * @type {Readonly<Record<string, { name: string, family: string, register: string, fixedTokenNote: string, terms: Record<string, string> }>>}
465
+ */
466
+ export const TRANSLATION_REGISTER = Object.freeze({
467
+ sv: {
468
+ name: 'Swedish',
469
+ family: 'Nordic',
470
+ register: 'Formal, impersonal (man / Europaparlamentet); avoid du-tilltal.',
471
+ fixedTokenNote:
472
+ "IMF/WEO verbatim — never IVF or 'Internationella valutafonden'. World Bank/Fiscal Monitor verbatim.",
473
+ terms: {
474
+ 'European Parliament': 'Europaparlamentet',
475
+ 'Plenary session': 'plenarsammanträde',
476
+ Committee: 'utskott',
477
+ Rapporteur: 'föredragande',
478
+ 'Legislative procedure': 'lagstiftningsförfarande',
479
+ Resolution: 'resolution',
480
+ 'Adopted text': 'antagen text',
481
+ Vote: 'omröstning',
482
+ },
483
+ },
484
+ da: {
485
+ name: 'Danish',
486
+ family: 'Nordic',
487
+ register: 'Formal, indicative mood; official EP designations.',
488
+ fixedTokenNote:
489
+ "IMF/WEO verbatim — never IMV or 'Den Internationale Valutafond'. World Bank/Fiscal Monitor verbatim.",
490
+ terms: {
491
+ 'European Parliament': 'Europa-Parlamentet',
492
+ 'Plenary session': 'plenarmøde',
493
+ Committee: 'udvalg',
494
+ Rapporteur: 'ordfører',
495
+ 'Legislative procedure': 'lovgivningsprocedure',
496
+ Resolution: 'beslutning',
497
+ 'Adopted text': 'vedtaget tekst',
498
+ Vote: 'afstemning',
499
+ },
500
+ },
501
+ no: {
502
+ name: 'Norwegian',
503
+ family: 'Nordic',
504
+ register: 'Formal, indicative mood; official EP designations.',
505
+ fixedTokenNote:
506
+ "IMF/WEO verbatim — never IPF/IMV/'Det internasjonale valutafondet'/Pengefondet/'Verdens økonomiske utsikter'. World Bank/Fiscal Monitor verbatim.",
507
+ terms: {
508
+ 'European Parliament': 'Europaparlamentet',
509
+ 'Plenary session': 'plenumsmøte',
510
+ Committee: 'komité',
511
+ Rapporteur: 'ordfører',
512
+ 'Legislative procedure': 'lovgivningsprosedyre',
513
+ Resolution: 'resolusjon',
514
+ 'Adopted text': 'vedtatt tekst',
515
+ Vote: 'avstemning',
516
+ },
517
+ },
518
+ fi: {
519
+ name: 'Finnish',
520
+ family: 'Nordic',
521
+ register: 'Formal; partitive + case agreement non-negotiable.',
522
+ fixedTokenNote:
523
+ "IMF/WEO verbatim — never KVR or 'Kansainvälinen valuuttarahasto'. World Bank/Fiscal Monitor verbatim.",
524
+ terms: {
525
+ 'European Parliament': 'Euroopan parlamentti',
526
+ 'Plenary session': 'täysistunto',
527
+ Committee: 'valiokunta',
528
+ Rapporteur: 'esittelijä',
529
+ 'Legislative procedure': 'lainsäädäntömenettely',
530
+ Resolution: 'päätöslauselma',
531
+ 'Adopted text': 'hyväksytty teksti',
532
+ Vote: 'äänestys',
533
+ },
534
+ },
535
+ de: {
536
+ name: 'German',
537
+ family: 'EU-core',
538
+ register:
539
+ "Formal (Sie); capitalise nouns; 'Europäisches Parlament' not 'EU-Parlament' in titles.",
540
+ fixedTokenNote:
541
+ 'IMF/WEO/World Bank/Fiscal Monitor verbatim — never IWF.',
542
+ terms: {
543
+ 'European Parliament': 'Europäisches Parlament',
544
+ 'Plenary session': 'Plenarsitzung',
545
+ Committee: 'Ausschuss',
546
+ Rapporteur: 'Berichterstatter',
547
+ 'Legislative procedure': 'Gesetzgebungsverfahren',
548
+ Resolution: 'Entschließung',
549
+ 'Adopted text': 'angenommener Text',
550
+ Vote: 'Abstimmung',
551
+ },
552
+ },
553
+ fr: {
554
+ name: 'French',
555
+ family: 'EU-core',
556
+ register:
557
+ 'Formal (vous); Académie française register; avoid Anglicisms unless source uses one as a fixed token.',
558
+ fixedTokenNote:
559
+ 'IMF/WEO/World Bank/Fiscal Monitor verbatim — never FMI.',
560
+ terms: {
561
+ 'European Parliament': 'Parlement européen',
562
+ 'Plenary session': 'séance plénière',
563
+ Committee: 'commission',
564
+ Rapporteur: 'rapporteur',
565
+ 'Legislative procedure': 'procédure législative',
566
+ Resolution: 'résolution',
567
+ 'Adopted text': 'texte adopté',
568
+ Vote: 'vote',
569
+ },
570
+ },
571
+ es: {
572
+ name: 'Spanish',
573
+ family: 'EU-core',
574
+ register: 'Peninsular es-ES; formal; avoid Latin-American vocabulary.',
575
+ fixedTokenNote:
576
+ 'IMF/WEO/World Bank/Fiscal Monitor verbatim — never FMI.',
577
+ terms: {
578
+ 'European Parliament': 'Parlamento Europeo',
579
+ 'Plenary session': 'sesión plenaria',
580
+ Committee: 'comisión',
581
+ Rapporteur: 'ponente',
582
+ 'Legislative procedure': 'procedimiento legislativo',
583
+ Resolution: 'resolución',
584
+ 'Adopted text': 'texto aprobado',
585
+ Vote: 'votación',
586
+ },
587
+ },
588
+ nl: {
589
+ name: 'Dutch',
590
+ family: 'EU-core',
591
+ register: 'Formal (u-form); prefer impersonal constructions.',
592
+ fixedTokenNote:
593
+ "IMF/WEO/World Bank/Fiscal Monitor verbatim — never IMV/'Internationaal Monetair Fonds'/Wereldbank.",
594
+ terms: {
595
+ 'European Parliament': 'Europees Parlement',
596
+ 'Plenary session': 'plenaire vergadering',
597
+ Committee: 'commissie',
598
+ Rapporteur: 'rapporteur',
599
+ 'Legislative procedure': 'wetgevingsprocedure',
600
+ Resolution: 'resolutie',
601
+ 'Adopted text': 'aangenomen tekst',
602
+ Vote: 'stemming',
603
+ },
604
+ },
605
+ ar: {
606
+ name: 'Arabic',
607
+ family: 'RTL',
608
+ register:
609
+ 'Modern Standard Arabic, formal political register; Western Arabic numerals 0–9; no RLM unless disambiguating a Latin token.',
610
+ fixedTokenNote:
611
+ 'Keep IMF/WEO/World Bank/Fiscal Monitor and EP acronyms in Latin script; data-vintage/TA-id/procedure-id verbatim.',
612
+ terms: {
613
+ 'European Parliament': 'البرلمان الأوروبي',
614
+ 'Plenary session': 'الجلسة العامة',
615
+ Committee: 'اللجنة',
616
+ Rapporteur: 'المقرر',
617
+ 'Legislative procedure': 'الإجراء التشريعي',
618
+ Resolution: 'قرار',
619
+ Vote: 'تصويت',
620
+ },
621
+ },
622
+ he: {
623
+ name: 'Hebrew',
624
+ family: 'RTL',
625
+ register: 'Formal modern Hebrew; no nikud unless the source carries it.',
626
+ fixedTokenNote:
627
+ 'Keep IMF/WEO/World Bank/Fiscal Monitor and EP acronyms in Latin script; data-vintage/TA-id/procedure-id verbatim.',
628
+ terms: {
629
+ 'European Parliament': 'הפרלמנט האירופי',
630
+ 'Plenary session': 'מליאה',
631
+ Committee: 'ועדה',
632
+ Rapporteur: 'מדווח',
633
+ 'Legislative procedure': 'הליך חקיקה',
634
+ Resolution: 'החלטה',
635
+ Vote: 'הצבעה',
636
+ },
637
+ },
638
+ ja: {
639
+ name: 'Japanese',
640
+ family: 'CJK',
641
+ register: 'です・ます polite register; full-width punctuation 。、「」.',
642
+ fixedTokenNote:
643
+ 'Keep proper nouns (IMF, WEO, World Bank, EP body names) in Latin script; data-vintage/TA-id/procedure-id verbatim.',
644
+ terms: {
645
+ 'European Parliament': '欧州議会',
646
+ 'Plenary session': '本会議',
647
+ Committee: '委員会',
648
+ Rapporteur: '報告者',
649
+ 'Legislative procedure': '立法手続き',
650
+ Resolution: '決議',
651
+ Vote: '採決',
652
+ },
653
+ },
654
+ ko: {
655
+ name: 'Korean',
656
+ family: 'CJK',
657
+ register: '합쇼체 formal polite; half-width , . ; spaces between eojeol.',
658
+ fixedTokenNote:
659
+ 'Keep proper nouns (IMF, WEO, World Bank, EP body names) in Latin script; data-vintage/TA-id/procedure-id verbatim.',
660
+ terms: {
661
+ 'European Parliament': '유럽의회',
662
+ 'Plenary session': '본회의',
663
+ Committee: '위원회',
664
+ Rapporteur: '보고자',
665
+ 'Legislative procedure': '입법절차',
666
+ Resolution: '결의',
667
+ Vote: '표결',
668
+ },
669
+ },
670
+ zh: {
671
+ name: 'Chinese',
672
+ family: 'CJK',
673
+ register: 'Simplified zh-CN only; full-width punctuation 。,「」/“”.',
674
+ fixedTokenNote:
675
+ 'Keep proper nouns (IMF, WEO, World Bank, EP body names) in Latin script; data-vintage/TA-id/procedure-id verbatim.',
676
+ terms: {
677
+ 'European Parliament': '欧洲议会',
678
+ 'Plenary session': '全体会议',
679
+ Committee: '委员会',
680
+ Rapporteur: '报告员',
681
+ 'Legislative procedure': '立法程序',
682
+ Resolution: '决议',
683
+ Vote: '表决',
684
+ },
685
+ },
686
+ });
687
+
688
+ /**
689
+ * Build the per-language register block emitted into the discovery payload.
690
+ * Returns only the entries for `langs` (the languages the run actually
691
+ * touches), so the agent's in-context copy stays as small as possible.
692
+ * Unknown codes are skipped silently — the caller derives `langs` from the
693
+ * canonical TARGET_LANGS union, so this is defensive only.
694
+ *
695
+ * @param {Iterable<string>} langs
696
+ * @returns {Record<string, (typeof TRANSLATION_REGISTER)[string]>}
697
+ */
698
+ export function buildTranslationRegister(langs) {
699
+ const out = {};
700
+ for (const lang of langs) {
701
+ if (Object.prototype.hasOwnProperty.call(TRANSLATION_REGISTER, lang)) {
702
+ out[lang] = TRANSLATION_REGISTER[lang];
703
+ }
704
+ }
705
+ return out;
706
+ }
707
+
429
708
  /**
430
709
  * Count the number of lines in a markdown source file. Returns 0 if the
431
710
  * file is missing. Used by `buildQueue` to populate `sourceLineCount` and
@@ -640,6 +919,20 @@ export function buildQueue(sources, options) {
640
919
  }
641
920
  const queuedTranslations = queue.reduce((sum, item) => sum + item.missingCount, 0);
642
921
 
922
+ // Per-language register the run actually needs: the union of every queued
923
+ // brief's missing languages, in canonical TARGET_LANGS order. Emitting this
924
+ // once (rather than per-entry) lets the translator agent read the style
925
+ // register + EP terminology pairs inline from queue.json and skip opening
926
+ // the ~18 KB translator guide in-session — bounding the per-session
927
+ // effective-token accumulation (see news-translate.md frontmatter budget).
928
+ const queuedLangs = new Set();
929
+ for (const item of queue) {
930
+ for (const lang of item.missingLangs) queuedLangs.add(lang);
931
+ }
932
+ const translationRegister = buildTranslationRegister(
933
+ TARGET_LANGS.filter((lang) => queuedLangs.has(lang)),
934
+ );
935
+
643
936
  // Top 3 most-blocked target languages across the entire backlog. Operators
644
937
  // skim this to spot e.g. "Japanese keeps falling behind" without parsing
645
938
  // the full queue. Sort by count desc, then language code asc for stable
@@ -675,6 +968,7 @@ export function buildQueue(sources, options) {
675
968
  largeSourceCount,
676
969
  maxSourceLines: opts.maxSourceLines,
677
970
  },
971
+ translationRegister,
678
972
  queue,
679
973
  };
680
974
  }
@@ -689,7 +983,7 @@ export function main(argv) {
689
983
  includeExtended: opts.includeExtended,
690
984
  maxAgeDays: opts.maxAgeDays,
691
985
  });
692
- const { totals, queue } = buildQueue(sources, {
986
+ const { totals, translationRegister, queue } = buildQueue(sources, {
693
987
  maxBriefs: opts.maxBriefs,
694
988
  mode: opts.mode,
695
989
  runNumber: opts.runNumber,
@@ -708,6 +1002,7 @@ export function main(argv) {
708
1002
  targetBrief: opts.targetBrief,
709
1003
  },
710
1004
  totals,
1005
+ translationRegister,
711
1006
  queue,
712
1007
  };
713
1008
  const out = `${JSON.stringify(payload, null, 2)}\n`;