From 53c060c8c252779a74100eb24ca5b51022a845c0 Mon Sep 17 00:00:00 2001 From: Jonathan Clarkin Date: Wed, 3 Dec 2025 15:17:15 -0500 Subject: [PATCH] Add year-based routing for provincial and municipal jurisdictions - New URL structure: /provincial/{province}/{year} and /municipal/{province}/{municipality}/{year} - Non-year URLs render latest year without redirect - Canonical links point to year-specific URLs for SEO - 301 redirects for old URL patterns - Consolidated duplicate page logic into shared components --- next.config.ts | 78 ++- scripts/test-url-structure.js | 469 +++++++++++++++++ .../[lang]/(main)/articles/[slug]/page.tsx | 2 +- src/app/[lang]/(main)/budget/page.tsx | 313 +----------- .../[lang]/(main)/federal/budget/layout.tsx | 28 + src/app/[lang]/(main)/federal/budget/page.tsx | 335 ++++++++++++ .../spending/SalaryDistributionChart.tsx | 0 .../{ => federal}/spending/TenureChart.tsx | 0 .../[lang]/(main)/federal/spending/layout.tsx | 28 + .../[lang]/(main)/federal/spending/page.tsx | 303 +++++++++++ .../[year]/departments/[department]/page.tsx | 429 ++++++++++++++++ .../[province]/[municipality]/[year]/page.tsx | 93 ++++ .../[province]/[municipality]/page.tsx | 82 +++ .../[year]/departments}/[department]/page.tsx | 49 +- .../provincial/[province]/[year]/page.tsx | 72 +++ .../(main)/provincial/[province]/page.tsx | 74 +++ src/app/[lang]/(main)/spending/page.tsx | 286 +---------- .../[lang]/(main)/tax-visualizer/layout.tsx | 28 + src/app/[lang]/(main)/tax-visualizer/page.tsx | 2 +- .../[year]/spending-full-screen/page.tsx | 55 ++ .../[year]/spending-full-screen/page.tsx | 41 ++ src/app/[lang]/layout.tsx | 47 +- src/app/layout.tsx | 53 ++ src/app/sitemap.ts | 145 +++--- src/components/DepartmentList.tsx | 15 +- .../JurisdictionPageContent.tsx} | 52 +- src/components/MainLayout/index.tsx | 32 +- src/components/Sankey/SankeyChart.tsx | 2 +- .../SpendingFullScreen.tsx} | 22 +- src/hooks/useDepartments.ts | 28 +- src/lib/jurisdictions.ts | 481 ++++++++++++------ src/lib/utils.ts | 1 - src/locales/en.po | 331 ++++++------ src/locales/fr.po | 333 ++++++------ 34 files changed, 3069 insertions(+), 1240 deletions(-) create mode 100755 scripts/test-url-structure.js create mode 100644 src/app/[lang]/(main)/federal/budget/layout.tsx create mode 100644 src/app/[lang]/(main)/federal/budget/page.tsx rename src/app/[lang]/(main)/{ => federal}/spending/SalaryDistributionChart.tsx (100%) rename src/app/[lang]/(main)/{ => federal}/spending/TenureChart.tsx (100%) create mode 100644 src/app/[lang]/(main)/federal/spending/layout.tsx create mode 100644 src/app/[lang]/(main)/federal/spending/page.tsx create mode 100644 src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx create mode 100644 src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx create mode 100644 src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx rename src/app/[lang]/(main)/{[jurisdiction] => provincial/[province]/[year]/departments}/[department]/page.tsx (93%) create mode 100644 src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx create mode 100644 src/app/[lang]/(main)/provincial/[province]/page.tsx create mode 100644 src/app/[lang]/(main)/tax-visualizer/layout.tsx create mode 100644 src/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx create mode 100644 src/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx create mode 100644 src/app/layout.tsx rename src/{app/[lang]/(main)/[jurisdiction]/page.tsx => components/JurisdictionPageContent.tsx} (94%) rename src/{app/[lang]/(mobile)/[jurisdiction]/spending-full-screen/page.tsx => components/SpendingFullScreen.tsx} (59%) diff --git a/next.config.ts b/next.config.ts index b70d5548..0bc49895 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,10 @@ import type { NextConfig } from "next"; import createMDX from "@next/mdx"; import rehypeSlug from "rehype-slug"; +import { + getProvincialSlugs, + getMunicipalitiesByProvince, +} from "./src/lib/jurisdictions"; const nextConfig: NextConfig = { /* config options here */ @@ -36,13 +40,85 @@ const nextConfig: NextConfig = { return config; }, async redirects() { - return [ + const redirects: Array<{ + source: string; + destination: string; + permanent: boolean; + }> = [ + // Permanent redirects (keep these) { source: "/:locale/tax-calculator", destination: "/:locale/tax-visualizer", permanent: true, }, ]; + + // ======================================================================== + // TEMPORARY REDIRECTS FOR OLD URL STRUCTURE + // TODO: REMOVE AFTER 2026-03-01 + // These redirects support backward compatibility for the old URL structure + // that existed before the provincial/municipal/federal restructuring. + // After March 1, 2026, these can be safely removed as users will have + // had sufficient time to update bookmarks and external links. + // ======================================================================== + + // Load jurisdiction data (uses cached static-data.json internally) + const provinces = getProvincialSlugs(); + const municipalitiesByProvince = getMunicipalitiesByProvince(); + + // Redirect old federal spending/budget URLs to new structure + redirects.push( + { + source: "/:locale/spending", + destination: "/:locale/federal/spending", + permanent: true, + }, + { + source: "/:locale/budget", + destination: "/:locale/federal/budget", + permanent: true, + }, + ); + + // Add redirects for old provincial URLs + for (const province of provinces) { + redirects.push( + { + source: `/:locale/${province}`, + destination: `/:locale/provincial/${province}`, + permanent: true, + }, + { + source: `/:locale/${province}/:department*`, + destination: `/:locale/provincial/${province}/:department*`, + permanent: true, + }, + ); + } + + // Add redirects for old municipal URLs + for (const { province, municipalities } of municipalitiesByProvince) { + for (const municipality of municipalities) { + redirects.push( + { + source: `/:locale/${municipality.slug}`, + destination: `/:locale/municipal/${province}/${municipality.slug}`, + permanent: true, + }, + { + source: `/:locale/${municipality.slug}/:path*`, + destination: `/:locale/municipal/${province}/${municipality.slug}/:path*`, + permanent: true, + }, + ); + } + } + + // ======================================================================== + // END TEMPORARY REDIRECTS - REMOVE AFTER 2026-03-01 + // ======================================================================== + + return redirects; }, async rewrites() { return [ diff --git a/scripts/test-url-structure.js b/scripts/test-url-structure.js new file mode 100755 index 00000000..16e39259 --- /dev/null +++ b/scripts/test-url-structure.js @@ -0,0 +1,469 @@ +#!/usr/bin/env node + +/** + * URL Structure Validation Script + * + * Tests: + * - robots.txt contains sitemap reference + * - sitemap.xml is well-formed + * - All sitemap URLs render correctly + * - Canonical and hreflang tags are present + * - 301 redirects work as expected + * + * Usage: node test-url-structure.js [base-url] + * Example: node test-url-structure.js http://localhost:3000 + */ + +const BASE_URL = process.argv[2] || "http://localhost:3000"; +const MAX_URLS_TO_TEST = parseInt(process.argv[3]) || 100; // Limit for faster testing + +// ANSI color codes +const colors = { + reset: "\x1b[0m", + bright: "\x1b[1m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + cyan: "\x1b[36m", +}; + +const stats = { + passed: 0, + failed: 0, + warnings: 0, + errors: [], +}; + +function log(message, color = colors.reset) { + console.log(`${color}${message}${colors.reset}`); +} + +function success(message) { + stats.passed++; + log(`✓ ${message}`, colors.green); +} + +function fail(message, error = "") { + stats.failed++; + stats.errors.push({ message, error }); + log(`✗ ${message}`, colors.red); + if (error) log(` ${error}`, colors.red); +} + +function warn(message) { + stats.warnings++; + log(`⚠ ${message}`, colors.yellow); +} + +function info(message) { + log(message, colors.cyan); +} + +function section(title) { + log(`\n${"=".repeat(60)}`, colors.blue); + log(title, colors.bright + colors.blue); + log("=".repeat(60), colors.blue); +} + +async function fetchText(url) { + const response = await fetch(url); + return { + status: response.status, + text: await response.text(), + headers: response.headers, + }; +} + +// Test 1: Check robots.txt +async function testRobotsTxt() { + section("TEST 1: robots.txt"); + + try { + const { status, text } = await fetchText(`${BASE_URL}/robots.txt`); + + if (status !== 200) { + fail(`robots.txt returned ${status}`, "Expected 200"); + return; + } + success("robots.txt is accessible"); + + if (text.includes("sitemap.xml") || text.includes("Sitemap:")) { + success("robots.txt references sitemap.xml"); + } else { + fail("robots.txt does not reference sitemap.xml"); + } + + info(`Content:\n${text}`); + } catch (error) { + fail("Failed to fetch robots.txt", error.message); + } +} + +// Test 2: Check sitemap.xml +async function testSitemap() { + section("TEST 2: sitemap.xml"); + + try { + const { status, text } = await fetchText(`${BASE_URL}/sitemap.xml`); + + if (status !== 200) { + fail(`sitemap.xml returned ${status}`, "Expected 200"); + return []; + } + success("sitemap.xml is accessible"); + + // Check if it's valid XML + if (!text.includes("(.*?)<\/loc>/g); + const urls = Array.from(urlMatches).map((match) => match[1]); + + if (urls.length === 0) { + fail("sitemap.xml contains no URLs"); + return []; + } + success(`sitemap.xml contains ${urls.length} URLs`); + + // Check for required URL patterns + const hasProvincial = urls.some((u) => u.includes("/provincial/")); + const hasMunicipal = urls.some((u) => u.includes("/municipal/")); + const hasFederal = urls.some((u) => u.includes("/federal/")); + + if (hasProvincial) success("sitemap includes provincial URLs"); + else warn("sitemap missing provincial URLs"); + + if (hasMunicipal) success("sitemap includes municipal URLs"); + else warn("sitemap missing municipal URLs"); + + if (hasFederal) success("sitemap includes federal URLs"); + else warn("sitemap missing federal URLs"); + + return urls; + } catch (error) { + fail("Failed to fetch sitemap.xml", error.message); + return []; + } +} + +// Test 3: Check individual URLs from sitemap +async function testSitemapUrls(urls) { + section( + `TEST 3: Sitemap URLs (testing ${Math.min(urls.length, MAX_URLS_TO_TEST)} of ${urls.length})`, + ); + + const urlsToTest = urls.slice(0, MAX_URLS_TO_TEST); + + for (const url of urlsToTest) { + try { + // Replace canadaspends.com with our BASE_URL for local testing + const testUrl = url.replace("https://canadaspends.com", BASE_URL); + + const { status, text } = await fetchText(testUrl); + + if (status !== 200) { + fail(`${url} returned ${status}`); + continue; + } + + // Check for canonical link + const canonicalMatch = text.match( + /]*rel=["']canonical["'][^>]*href=["']([^"']+)["']/, + ); + const hasCanonical = canonicalMatch !== null; + + // Check for hreflang links (case-insensitive for hreflang/hrefLang) + const hreflangMatches = text.match( + /]*rel=["']alternate["'][^>]*hreflang=["']([^"']+)["']/gi, + ); + const hasHreflang = hreflangMatches && hreflangMatches.length > 0; + + // Extract just the path for display + const urlPath = testUrl.replace(BASE_URL, ""); + + if (status === 200 && hasCanonical && hasHreflang) { + success(`${urlPath}`); + } else { + const issues = []; + if (!hasCanonical) issues.push("no canonical"); + if (!hasHreflang) issues.push("no hreflang"); + warn(`${urlPath} - ${issues.join(", ")}`); + } + } catch (error) { + fail(`${url}`, error.message); + } + } + + if (urls.length > MAX_URLS_TO_TEST) { + info( + `\n(Tested ${MAX_URLS_TO_TEST} of ${urls.length} URLs. Set higher limit with: node test-url-structure.js ${BASE_URL} 100)`, + ); + } +} + +// Test 4: Check 301 redirects +async function testRedirects() { + section("TEST 4: 301 Redirects"); + + const redirectTests = [ + // Federal + { + from: "/en/spending", + to: "/en/federal/spending", + desc: "Federal spending", + }, + { from: "/en/budget", to: "/en/federal/budget", desc: "Federal budget" }, + { + from: "/fr/spending", + to: "/fr/federal/spending", + desc: "Federal spending (FR)", + }, + { + from: "/fr/budget", + to: "/fr/federal/budget", + desc: "Federal budget (FR)", + }, + + // Provincial + { + from: "/en/ontario", + to: "/en/provincial/ontario", + desc: "Ontario provincial", + }, + { + from: "/en/british-columbia", + to: "/en/provincial/british-columbia", + desc: "BC provincial", + }, + { + from: "/en/alberta", + to: "/en/provincial/alberta", + desc: "Alberta provincial", + }, + + // Municipal + { + from: "/en/toronto", + to: "/en/municipal/ontario/toronto", + desc: "Toronto municipal", + }, + { + from: "/en/vancouver", + to: "/en/municipal/british-columbia/vancouver", + desc: "Vancouver municipal", + }, + ]; + + for (const test of redirectTests) { + try { + const response = await fetch(`${BASE_URL}${test.from}`, { + redirect: "manual", + }); + + if (response.status === 301 || response.status === 308) { + const location = response.headers.get("location"); + const expectedLocation = `${BASE_URL}${test.to}`; + + if (location === test.to || location === expectedLocation) { + success(`${test.from} → ${test.to} (${response.status})`); + } else { + fail( + `${test.from} redirects to wrong location`, + `Expected: ${test.to}, Got: ${location}`, + ); + } + } else if (response.status === 200) { + fail( + `${test.from} returned 200 instead of redirect`, + "Should be 301/308", + ); + } else { + fail(`${test.from} returned ${response.status}`, "Expected 301 or 308"); + } + } catch (error) { + fail(`${test.from}`, error.message); + } + } +} + +// Test 5: Check non-year URLs render correctly +async function testNonYearUrls() { + section("TEST 5: Non-Year URLs (Latest Year Rendering)"); + + const nonYearTests = [ + { + url: "/en/provincial/ontario", + desc: "Ontario provincial (non-year)", + shouldHaveCanonical: true, + canonicalPattern: /\/en\/provincial\/ontario\/\d{4}/, + }, + { + url: "/en/municipal/ontario/toronto", + desc: "Toronto municipal (non-year)", + shouldHaveCanonical: true, + canonicalPattern: /\/en\/municipal\/ontario\/toronto\/\d{4}/, + }, + { + url: "/en/provincial/british-columbia", + desc: "BC provincial (non-year)", + shouldHaveCanonical: true, + canonicalPattern: /\/en\/provincial\/british-columbia\/\d{4}/, + }, + ]; + + for (const test of nonYearTests) { + try { + const response = await fetch(`${BASE_URL}${test.url}`, { + redirect: "manual", + }); + + // Should NOT redirect + if (response.status >= 300 && response.status < 400) { + fail( + `${test.url} redirected (${response.status})`, + "Should render directly without redirect", + ); + continue; + } + + if (response.status !== 200) { + fail(`${test.url} returned ${response.status}`, "Expected 200"); + continue; + } + + success(`${test.url} renders without redirect (200)`); + + // Check canonical + const html = await response.text(); + const canonicalMatch = html.match( + /]*rel=["']canonical["'][^>]*href=["']([^"']+)["']/, + ); + + if (test.shouldHaveCanonical) { + if (canonicalMatch) { + const canonical = canonicalMatch[1]; + if (test.canonicalPattern.test(canonical)) { + success( + ` Canonical link points to year-specific URL: ${canonical}`, + ); + } else { + fail( + ` Canonical link doesn't match expected pattern`, + `Got: ${canonical}`, + ); + } + } else { + fail(` Missing canonical link`); + } + } + + // Check hreflang (case-insensitive for hreflang/hrefLang) + const hreflangMatches = html.match( + /]*rel=["']alternate["'][^>]*hreflang=["']([^"']+)["']/gi, + ); + if (hreflangMatches && hreflangMatches.length >= 2) { + success(` Has hreflang alternates (${hreflangMatches.length})`); + } else { + warn(` Missing or incomplete hreflang alternates`); + } + } catch (error) { + fail(`${test.url}`, error.message); + } + } +} + +// Test 6: Check year-specific URLs +async function testYearSpecificUrls() { + section("TEST 6: Year-Specific URLs"); + + const yearTests = [ + { url: "/en/provincial/ontario/2023", desc: "Ontario 2023" }, + { url: "/en/municipal/ontario/toronto/2024", desc: "Toronto 2024" }, + { url: "/en/provincial/british-columbia/2024", desc: "BC 2024" }, + ]; + + for (const test of yearTests) { + try { + const response = await fetch(`${BASE_URL}${test.url}`); + + if (response.status === 200) { + success(`${test.url} (200)`); + } else if (response.status === 404) { + warn(`${test.url} (404) - Year may not have data yet`); + } else { + fail(`${test.url} returned ${response.status}`); + } + } catch (error) { + fail(`${test.url}`, error.message); + } + } +} + +// Main test runner +async function runTests() { + log(`\n${"*".repeat(60)}`, colors.bright); + log("URL STRUCTURE VALIDATION TEST SUITE", colors.bright + colors.cyan); + log("*".repeat(60), colors.bright); + log(`\nBase URL: ${BASE_URL}`, colors.cyan); + log(`Time: ${new Date().toISOString()}\n`, colors.cyan); + + // Check if server is running + try { + await fetch(BASE_URL); + } catch { + log(`\n❌ Cannot connect to ${BASE_URL}`, colors.red); + log("Make sure the dev server is running: npm run dev\n", colors.yellow); + process.exit(1); + } + + await testRobotsTxt(); + + const urls = await testSitemap(); + + if (urls.length > 0) { + await testSitemapUrls(urls); + } + + await testRedirects(); + await testNonYearUrls(); + await testYearSpecificUrls(); + + // Print summary + section("TEST SUMMARY"); + log(`Passed: ${stats.passed}`, colors.green); + log(`Failed: ${stats.failed}`, colors.red); + log(`Warnings: ${stats.warnings}`, colors.yellow); + + if (stats.errors.length > 0) { + log("\nFailed Tests:", colors.red); + stats.errors.forEach((error, i) => { + log(`${i + 1}. ${error.message}`, colors.red); + if (error.error) log(` ${error.error}`, colors.red); + }); + } + + log("\n" + "=".repeat(60) + "\n", colors.blue); + + if (stats.failed > 0) { + log("❌ TESTS FAILED", colors.red + colors.bright); + process.exit(1); + } else if (stats.warnings > 0) { + log("⚠️ TESTS PASSED WITH WARNINGS", colors.yellow + colors.bright); + } else { + log("✅ ALL TESTS PASSED", colors.green + colors.bright); + } + + console.log(""); +} + +// Run tests +runTests().catch((error) => { + log(`\n❌ Fatal error: ${error.message}`, colors.red); + console.error(error); + process.exit(1); +}); diff --git a/src/app/[lang]/(main)/articles/[slug]/page.tsx b/src/app/[lang]/(main)/articles/[slug]/page.tsx index 97450e97..8cc0ea11 100644 --- a/src/app/[lang]/(main)/articles/[slug]/page.tsx +++ b/src/app/[lang]/(main)/articles/[slug]/page.tsx @@ -10,6 +10,7 @@ import { } from "@/lib/articles"; import { initLingui } from "@/initLingui"; import { generateHreflangAlternates } from "@/lib/utils"; +import { locales } from "@/lib/constants"; import { Trans } from "@lingui/react/macro"; import { notFound } from "next/navigation"; import { FiClock, FiCalendar } from "react-icons/fi"; @@ -24,7 +25,6 @@ interface ArticlePageProps { // Generate static params for all articles (for static site generation) export async function generateStaticParams() { - const locales = ["en", "fr"]; const paths: { lang: string; slug: string }[] = []; for (const lang of locales) { diff --git a/src/app/[lang]/(main)/budget/page.tsx b/src/app/[lang]/(main)/budget/page.tsx index a8d5362a..d6475074 100644 --- a/src/app/[lang]/(main)/budget/page.tsx +++ b/src/app/[lang]/(main)/budget/page.tsx @@ -1,315 +1,18 @@ "use client"; -import { DepartmentList } from "@/components/DepartmentList"; -import { - ExternalLink, - H1, - H2, - InternalLink, - Intro, - P, - Page, - PageContent, - Section, -} from "@/components/Layout"; -import NoSSR from "@/components/NoSSR"; -import { BudgetSankey } from "@/components/Sankey/BudgetSankey"; -import { Trans, useLingui } from "@lingui/react/macro"; -import { useState, useCallback } from "react"; -import { NewsItem, budgetNewsData } from "@/lib/budgetNewsData"; -import { IS_BUDGET_2025_LIVE } from "@/lib/featureFlags"; -import Link from "next/link"; +import { BudgetPageContent } from "@/app/[lang]/(main)/federal/budget/page"; +import { useLingui } from "@lingui/react/macro"; import { localizedPath } from "@/lib/utils"; -const StatBox = ({ - title, - value, - description, - growthPercentage, -}: { - title: React.ReactNode; - value: string; - description: React.ReactNode; - growthPercentage?: number; -}) => ( -
-
{title}
-
{value}
-
{description}
- {growthPercentage && ( -
0 ? "text-green-600" : "text-red-600"}`} - > - {growthPercentage > 0 ? "+" : ""} - {growthPercentage}% over the last year -
- )} -
-); - -// Reusable News Table Component -const NewsTable = ({ newsData }: { newsData: NewsItem[] }) => ( -
- - - - - - - - - - {newsData.map((item) => ( - - - - - - ))} - -
- News Source & Date - - Budget Impact - - Amount/Change -
-
- - {item.source} - {item.date} - -

{item.headline}

-
-
- {item.budgetImpact} - - - {item.isIncrease ? "+" : "-"} - {item.amount} ({item.isIncrease ? "+" : ""} - {item.percentage}%) - -
-
-); - -const calculateGrowthPercentage = ( - current: number, - previous: number, -): number => { - if (previous === 0) return 0; - return Math.round(((current - previous) / previous) * 100 * 10) / 10; -}; - export default function Budget() { const { i18n } = useLingui(); - const [budgetData, setBudgetData] = useState({ - spending: 513.9, - revenue: 459.5, - deficit: 54.4, - opex2024: 0, - capex2024: 0, - opex2025: 0, - capex2025: 0, - transfers2024: 0, - transfers2025: 0, - debt2024: 0, - debt2025: 0, - other2024: 0, - other2025: 0, - }); - - const handleBudgetDataChange = useCallback( - (data: { - spending: number; - revenue: number; - deficit: number; - opex2024: number; - capex2024: number; - opex2025: number; - capex2025: number; - transfers2024: number; - transfers2025: number; - debt2024: number; - debt2025: number; - other2024: number; - other2025: number; - }) => { - setBudgetData(data); - }, - [], - ); return ( - - {/* Official Budget Banner - Only Show When Budget is Live */} - {IS_BUDGET_2025_LIVE && ( -
-
-

- Official Fall 2025 Federal Budget Released -

- - View Federal 2025 Budget Details - -
-
- )} - - -
-

- Federal Fall 2025 Government Budget -

- - {IS_BUDGET_2025_LIVE ? ( - - This page presents the official Fall 2025 Federal Budget as - released by the Government of Canada on November 4th, 2025. All - data is sourced directly from official government publications - and public accounts from the Government of Canada. - - ) : ( - - The values you see here are based on the FY 2024 Budget with - preliminary updates based on government announcements, memos, - and leaks, and are meant to provide a rough idea of the budget. - Once the official Fall 2025 Budget is released on November 4th, - we will update this page to reflect the official budget. - - )} - -
-
-

- Budget Statistics (Projected FY 2025) -

-
- Total Budget} - value={`$${budgetData.spending.toFixed(1)}B`} - description={Est. projected government budget} - growthPercentage={calculateGrowthPercentage( - budgetData.spending, - 513.9, - )} - /> - Revenue} - value={`$${budgetData.revenue.toFixed(1)}B`} - description={Est. projected government revenue} - growthPercentage={calculateGrowthPercentage( - budgetData.revenue, - 459.5, - )} - /> - {!IS_BUDGET_2025_LIVE && ( - <> - Operational Spend} - value={`$${budgetData.opex2025.toFixed(1)}B`} - description={Projected operational spending} - growthPercentage={calculateGrowthPercentage( - budgetData.opex2025, - budgetData.opex2024, - )} - /> - Capital Investments} - value={`$${budgetData.capex2025.toFixed(1)}B`} - description={Projected capital investments} - growthPercentage={calculateGrowthPercentage( - budgetData.capex2025, - budgetData.capex2024, - )} - /> - - )} - Deficit} - value={`$${budgetData.deficit.toFixed(1)}B`} - description={Est. projected budget deficit} - growthPercentage={calculateGrowthPercentage( - budgetData.deficit, - 54.4, - )} - /> -
-
-
- - - -
- - Source - -
-
- - View this chart in full screen - -
-
-
-

- Latest Budget News & Impact -

-

- - Recent developments and their projected impact on the Fall 2025 - Budget - -

- -
- -
-

- Government Departments Explained -

- -
-
-

- Sources -

-

- - All government budget data is sourced from official databases, but - due to the complexity of these systems, occasional errors may - occur despite our best efforts. This page is also based on - government memos and leaks, so it is not an official release of - the Fall 2025 Budget. We aim to make this information more - accessible and accurate, and we welcome feedback. If you notice - any issues, please let us know{" "} - - here - {" "} - — we appreciate it and will work to address them promptly. - -

-
-
-
+ ); } diff --git a/src/app/[lang]/(main)/federal/budget/layout.tsx b/src/app/[lang]/(main)/federal/budget/layout.tsx new file mode 100644 index 00000000..2236b327 --- /dev/null +++ b/src/app/[lang]/(main)/federal/budget/layout.tsx @@ -0,0 +1,28 @@ +import { generateHreflangAlternates } from "@/lib/utils"; +import { initLingui, PageLangParam } from "@/initLingui"; +import { useLingui } from "@lingui/react/macro"; +import { PropsWithChildren } from "react"; + +export async function generateMetadata( + props: PropsWithChildren, +) { + const lang = (await props.params).lang; + initLingui(lang); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const { t } = useLingui(); + + return { + title: t`Federal Budget 2025 | Canada Spends`, + description: t`Explore Canada's federal budget with interactive visualizations and detailed analysis of government revenue and expenditures.`, + alternates: generateHreflangAlternates(lang), + }; +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[lang]/(main)/federal/budget/page.tsx b/src/app/[lang]/(main)/federal/budget/page.tsx new file mode 100644 index 00000000..c827fe67 --- /dev/null +++ b/src/app/[lang]/(main)/federal/budget/page.tsx @@ -0,0 +1,335 @@ +"use client"; + +import { DepartmentList } from "@/components/DepartmentList"; +import { + ExternalLink, + H1, + H2, + InternalLink, + Intro, + P, + Page, + PageContent, + Section, +} from "@/components/Layout"; +import NoSSR from "@/components/NoSSR"; +import { BudgetSankey } from "@/components/Sankey/BudgetSankey"; +import { Trans } from "@lingui/react/macro"; +import { useState, useCallback } from "react"; +import { NewsItem, budgetNewsData } from "@/lib/budgetNewsData"; +import { IS_BUDGET_2025_LIVE } from "@/lib/featureFlags"; +import Link from "next/link"; + +const StatBox = ({ + title, + value, + description, + growthPercentage, +}: { + title: React.ReactNode; + value: string; + description: React.ReactNode; + growthPercentage?: number; +}) => ( +
+
{title}
+
{value}
+
{description}
+ {growthPercentage && ( +
0 ? "text-green-600" : "text-red-600"}`} + > + {growthPercentage > 0 ? "+" : ""} + {growthPercentage}% over the last year +
+ )} +
+); + +// Reusable News Table Component +const NewsTable = ({ newsData }: { newsData: NewsItem[] }) => ( +
+ + + + + + + + + + {newsData.map((item) => ( + + + + + + ))} + +
+ News Source & Date + + Budget Impact + + Amount/Change +
+
+ + {item.source} - {item.date} + +

{item.headline}

+
+
+ {item.budgetImpact} + + + {item.isIncrease ? "+" : "-"} + {item.amount} ({item.isIncrease ? "+" : ""} + {item.percentage}%) + +
+
+); + +const calculateGrowthPercentage = ( + current: number, + previous: number, +): number => { + if (previous === 0) return 0; + return Math.round(((current - previous) / previous) * 100 * 10) / 10; +}; + +type BudgetPageContentProps = { + budgetPath: string; + fullScreenPath: string; + contactPath: string; + locale?: string; +}; + +export function BudgetPageContent({ + budgetPath, + fullScreenPath, + contactPath, + locale, +}: BudgetPageContentProps) { + const [budgetData, setBudgetData] = useState({ + spending: 513.9, + revenue: 459.5, + deficit: 54.4, + opex2024: 0, + capex2024: 0, + opex2025: 0, + capex2025: 0, + transfers2024: 0, + transfers2025: 0, + debt2024: 0, + debt2025: 0, + other2024: 0, + other2025: 0, + }); + + const handleBudgetDataChange = useCallback( + (data: { + spending: number; + revenue: number; + deficit: number; + opex2024: number; + capex2024: number; + opex2025: number; + capex2025: number; + transfers2024: number; + transfers2025: number; + debt2024: number; + debt2025: number; + other2024: number; + other2025: number; + }) => { + setBudgetData(data); + }, + [], + ); + + return ( + + {/* Official Budget Banner - Only Show When Budget is Live */} + {IS_BUDGET_2025_LIVE && ( +
+
+

+ Official Fall 2025 Federal Budget Released +

+ + View Federal 2025 Budget Details + +
+
+ )} + + +
+

+ Federal Fall 2025 Government Budget +

+ + {IS_BUDGET_2025_LIVE ? ( + + This page presents the official Fall 2025 Federal Budget as + released by the Government of Canada on November 4th, 2025. All + data is sourced directly from official government publications + and public accounts from the Government of Canada. + + ) : ( + + The values you see here are based on the FY 2024 Budget with + preliminary updates based on government announcements, memos, + and leaks, and are meant to provide a rough idea of the budget. + Once the official Fall 2025 Budget is released on November 4th, + we will update this page to reflect the official budget. + + )} + +
+
+

+ Budget Statistics (Projected FY 2025) +

+
+ Total Budget} + value={`$${budgetData.spending.toFixed(1)}B`} + description={Est. projected government budget} + growthPercentage={calculateGrowthPercentage( + budgetData.spending, + 513.9, + )} + /> + Revenue} + value={`$${budgetData.revenue.toFixed(1)}B`} + description={Est. projected government revenue} + growthPercentage={calculateGrowthPercentage( + budgetData.revenue, + 459.5, + )} + /> + {!IS_BUDGET_2025_LIVE && ( + <> + Operational Spend} + value={`$${budgetData.opex2025.toFixed(1)}B`} + description={Projected operational spending} + growthPercentage={calculateGrowthPercentage( + budgetData.opex2025, + budgetData.opex2024, + )} + /> + Capital Investments} + value={`$${budgetData.capex2025.toFixed(1)}B`} + description={Projected capital investments} + growthPercentage={calculateGrowthPercentage( + budgetData.capex2025, + budgetData.capex2024, + )} + /> + + )} + Deficit} + value={`$${budgetData.deficit.toFixed(1)}B`} + description={Est. projected budget deficit} + growthPercentage={calculateGrowthPercentage( + budgetData.deficit, + 54.4, + )} + /> +
+
+
+ + + +
+ + Source + +
+
+ + View this chart in full screen + +
+
+
+

+ Latest Budget News & Impact +

+

+ + Recent developments and their projected impact on the Fall 2025 + Budget + +

+ +
+ +
+

+ Government Departments Explained +

+ +
+
+

+ Sources +

+

+ + All government budget data is sourced from official databases, but + due to the complexity of these systems, occasional errors may + occur despite our best efforts. This page is also based on + government memos and leaks, so it is not an official release of + the Fall 2025 Budget. We aim to make this information more + accessible and accurate, and we welcome feedback. If you notice + any issues, please let us know{" "} + + here + {" "} + — we appreciate it and will work to address them promptly. + +

+
+
+
+ ); +} + +export default function Budget() { + return ( + + ); +} diff --git a/src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx b/src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx similarity index 100% rename from src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx rename to src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx diff --git a/src/app/[lang]/(main)/spending/TenureChart.tsx b/src/app/[lang]/(main)/federal/spending/TenureChart.tsx similarity index 100% rename from src/app/[lang]/(main)/spending/TenureChart.tsx rename to src/app/[lang]/(main)/federal/spending/TenureChart.tsx diff --git a/src/app/[lang]/(main)/federal/spending/layout.tsx b/src/app/[lang]/(main)/federal/spending/layout.tsx new file mode 100644 index 00000000..91ba1bb1 --- /dev/null +++ b/src/app/[lang]/(main)/federal/spending/layout.tsx @@ -0,0 +1,28 @@ +import { generateHreflangAlternates } from "@/lib/utils"; +import { initLingui, PageLangParam } from "@/initLingui"; +import { useLingui } from "@lingui/react/macro"; +import { PropsWithChildren } from "react"; + +export async function generateMetadata( + props: PropsWithChildren, +) { + const lang = (await props.params).lang; + initLingui(lang); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const { t } = useLingui(); + + return { + title: t`Federal Government Spending | Canada Spends`, + description: t`Explore how the Canadian federal government spends your tax dollars across departments and programs.`, + alternates: generateHreflangAlternates(lang), + }; +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[lang]/(main)/federal/spending/page.tsx b/src/app/[lang]/(main)/federal/spending/page.tsx new file mode 100644 index 00000000..f6c53e63 --- /dev/null +++ b/src/app/[lang]/(main)/federal/spending/page.tsx @@ -0,0 +1,303 @@ +"use client"; + +import { TenureChart } from "@/app/[lang]/(main)/federal/spending/TenureChart"; +import { SalaryDistributionChart } from "@/app/[lang]/(main)/federal/spending/SalaryDistributionChart"; +import { BarChart } from "@/components/BarChart"; +import { DepartmentList } from "@/components/DepartmentList"; +import { + ExternalLink, + H1, + H2, + InternalLink, + Intro, + P, + Page, + PageContent, + Section, +} from "@/components/Layout"; +import NoSSR from "@/components/NoSSR"; +import { Sankey } from "@/components/Sankey"; +import { Trans, useLingui } from "@lingui/react/macro"; + +const StatBox = ({ + title, + value, + description, + growthPercentage, +}: { + title: React.ReactNode; + value: string; + description: React.ReactNode; + growthPercentage?: number; +}) => ( +
+
{title}
+
{value}
+
{description}
+ {growthPercentage && ( +
0 ? "text-green-600" : "text-red-600"}`} + > + {growthPercentage > 0 ? "+" : ""} + {growthPercentage}% over the last 5 years +
+ )} +
+); + +const ageData = [ + { name: "<20", Count: 1756 }, + { name: "20-24", Count: 33596 }, + { name: "25-29", Count: 78800 }, + { name: "30-34", Count: 89426 }, + { name: "35-39", Count: 187657 }, + { name: "40-44", Count: 216806 }, + { name: "45-49", Count: 216024 }, + { name: "50-54", Count: 190226 }, + { name: "55-59", Count: 146872 }, + { name: "60-64", Count: 84865 }, + { name: "65+", Count: 39494 }, +]; + +const calculateGrowthPercentage = (dataType: string) => { + const headcountData = [ + { Year: "2019", Value: 382107 }, + { Year: "2020", Value: 390798 }, + { Year: "2021", Value: 413424 }, + { Year: "2022", Value: 431739 }, + { Year: "2023", Value: 440985 }, + ]; + + const wagesData = [ + { Year: "2019", Value: 46.3 }, + { Year: "2020", Value: 53.0 }, + { Year: "2021", Value: 60.7 }, + { Year: "2022", Value: 56.5 }, + { Year: "2023", Value: 65.3 }, + ]; + + const compensationData = [ + { Year: "2019", Value: 117522 }, + { Year: "2020", Value: 123163 }, + { Year: "2021", Value: 125256 }, + { Year: "2022", Value: 126634 }, + { Year: "2023", Value: 136345 }, + ]; + + if (dataType === "headcount") { + const oldValue = headcountData[0].Value; + const newValue = headcountData[headcountData.length - 1].Value; + return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); + } else if (dataType === "wages") { + const oldValue = wagesData[0].Value; + const newValue = wagesData[wagesData.length - 1].Value; + return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); + } else if (dataType === "compensation") { + const oldValue = compensationData[0].Value; + const newValue = compensationData[compensationData.length - 1].Value; + return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); + } + return 0; +}; + +type SpendingPageContentProps = { + fullScreenPath: string; + contactPath: string; + locale?: string; +}; + +export function SpendingPageContent({ + fullScreenPath, + contactPath, + locale, +}: SpendingPageContentProps) { + const { t } = useLingui(); + return ( + + +
+

+ Government Spending +

+ + + Get data-driven insights into how governmental revenue and + spending affect Canadian lives and programs. + + +
+
+

+ FY 2024 Government Revenue and Spending +

+

+ + Explore revenue and spending categories or filter by agency for + deeper insights. + +

+
+
+
+ + + +
+ + Source + +
+
+ + View this chart in full screen + +
+
+ +
+

+ Government Workforce +

+
+ + + + + + +
+

+ Type of Tenure +

+

+ 80% of employees are in permanent roles +

+
+ + + +
+
+ +
+

+ Age +

+

+ The average employee is 43.3 years old +

+
+ + + Intl.NumberFormat("en-US", { + notation: "compact", + }).format(Number(value)) + } + /> + +
+
+ + +

+ Sources:{" "} + + PBO + + ,{" "} + + Treasury Board + +

+ +
+

+ Salary Distribution +

+

+ + Explore federal employee salary distribution by year and + demographic group + +

+ + + +
+

+ Source:{" "} + + Treasury Board + +

+
+
+
+

+ Government Departments explained +

+ +
+
+

+ Sources +

+

+ + All government spending data is sourced from official databases, + but due to the complexity of these systems, occasional errors may + occur despite our best efforts. We aim to make this information + more accessible and accurate, and we welcome feedback. If you + notice any issues, please let us know{" "} + + here + {" "} + — we appreciate it and will work to address them promptly. + +

+
+
+
+ ); +} + +export default function Spending() { + return ( + + ); +} diff --git a/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx new file mode 100644 index 00000000..dd1edee4 --- /dev/null +++ b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx @@ -0,0 +1,429 @@ +import { + getDepartmentData, + getDepartmentsForJurisdiction, + getExpandedDepartments, + getJurisdictionData, + getMunicipalitiesByProvince, + getAvailableYearsForJurisdiction, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; + +export const dynamicParams = false; + +export async function generateStaticParams() { + const municipalitiesByProvince = getMunicipalitiesByProvince(); + + return locales.flatMap((lang) => + municipalitiesByProvince.flatMap(({ province, municipalities }) => + municipalities.flatMap((municipality) => { + const jurisdictionSlug = `${province}/${municipality.slug}`; + const years = getAvailableYearsForJurisdiction(jurisdictionSlug); + return years.flatMap((year) => { + const departments = getDepartmentsForJurisdiction( + jurisdictionSlug, + year, + ); + return departments.map((department) => ({ + lang, + province, + municipality: municipality.slug, + year, + department, + })); + }); + }), + ), + ); +} + +import { + ChartContainer, + H1, + H2, + Intro, + P, + Page, + PageContent, + Section, +} from "@/components/Layout"; +import { StatCard, StatCardContainer } from "@/components/StatCard"; +import { initLingui } from "@/initLingui"; +import { Trans } from "@lingui/react/macro"; +import { DepartmentMiniSankey } from "@/components/Sankey/DepartmentMiniSankey"; +import { JurisdictionDepartmentList } from "@/components/DepartmentList"; + +export default async function DepartmentPage({ + params, +}: { + params: Promise<{ + lang: string; + province: string; + municipality: string; + year: string; + department: string; + }>; +}) { + const { + lang, + province, + municipality, + year, + department: departmentSlug, + } = await params; + + initLingui(lang); + + const jurisdictionSlug = `${province}/${municipality}`; + const { jurisdiction } = getJurisdictionData(jurisdictionSlug, year); + const departments = getExpandedDepartments(jurisdictionSlug, year); + + const department = getDepartmentData(jurisdictionSlug, departmentSlug, year); + + return ( + + +
+

+ {department.name} +

+ {department.introText.split("\n\n").map((paragraph, index) => ( +
+ {paragraph.startsWith("•") ? ( +
+
    + {paragraph + .split("\n") + .filter((line) => line.trim().startsWith("•")) + .map((item, itemIndex) => { + const cleanItem = item.replace("• ", ""); + const parts = cleanItem.split(" – "); + return ( +
  • + {parts[0]} + {parts[1] && ` – ${parts[1]}`} +
  • + ); + })} +
+
+ ) : ( + + {paragraph.match(/\*\*([^*]+)\*\*/) || + paragraph.match(/\[([^\]]+)\]\(([^)]+)\)/) ? ( + $1") + .replace( + /\[([^\]]+)\]\(([^)]+)\)/g, + '$1', + ), + }} + /> + ) : ( + {paragraph} + )} + + )} +
+ ))} + +

+ Department Spending +

+ + + In Financial Year {jurisdiction.financialYear}, + } + value={department.totalSpendingFormatted} + subtitle={was spent by {department.name}} + /> + In Financial Year {jurisdiction.financialYear}, + } + value={department.percentageFormatted} + subtitle={ + + of {jurisdiction.name} municipal spending was by{" "} + {department.name} + + } + /> + + +
+ + {department.descriptionText.split("\n\n").map((paragraph, index) => ( +

+ {paragraph} +

+ ))} + + {department.roleText.split("\n\n").map((paragraph, index) => ( +

+ {paragraph.match(/\[([^\]]+)\]\(([^)]+)\)/) ? ( + $1', + ), + }} + /> + ) : ( + {paragraph} + )} +

+ ))} + + + + + +
+ + {department.budgetProjectionsText && ( +
+ {department.budgetProjectionsText + .split("\n\n") + .map((paragraph, index) => ( +

+ {paragraph.match(/\*\*([^*]+)\*\*/) || + paragraph.match(/\[([^\]]+)\]\(([^)]+)\)/) ? ( + $1") + .replace( + /\[([^\]]+)\]\(([^)]+)\)/g, + '$1', + ), + }} + /> + ) : ( + {paragraph} + )} +

+ ))} +
+ )} + +
+ + {department.agenciesHeading && department.agenciesDescription && ( +
+

+ {department.agenciesHeading} +

+
+ {department.agenciesDescription + .split("\n\n") + .map((paragraph, index) => ( +
+ {paragraph.startsWith("•") ? ( +
    + {paragraph + .split("\n") + .filter((line) => line.trim().startsWith("•")) + .map((item, itemIndex) => { + const cleanItem = item.replace("• ", ""); + const parts = cleanItem.split(" – "); + return ( +
  • + {parts[0].match(/\*\*([^*]+)\*\*/) ? ( + $1", + ), + }} + /> + ) : ( + {parts[0]} + )} + {parts[1] && ` – ${parts[1]}`} +
  • + ); + })} +
+ ) : ( +

+ {paragraph} +

+ )} +
+ ))} +
+
+ )} + + {department.programsDescription && ( +
+
+ {department.programsDescription + .split("\n\n") + .map((paragraph, index) => ( +
+ {paragraph.startsWith("•") ? ( +
    + {paragraph + .split("\n") + .filter((line) => line.trim().startsWith("•")) + .map((item, itemIndex) => { + const cleanItem = item.replace("• ", ""); + const parts = cleanItem.split(" – "); + return ( +
  • + {parts[0].match(/\*\*([^*]+)\*\*/) ? ( + $1", + ), + }} + /> + ) : ( + {parts[0]} + )} + {parts[1] && ` – ${parts[1]}`} +
  • + ); + })} +
+ ) : ( +

+ {paragraph} +

+ )} +
+ ))} +
+
+ )} + +
+ + {department.prioritiesHeading && department.prioritiesDescription && ( +
+

+ {department.prioritiesHeading} +

+
+ {department.prioritiesDescription + .split("\n\n") + .map((paragraph, index) => ( +
+ {paragraph.startsWith("•") ? ( +
    + {paragraph + .split("\n") + .filter((line) => line.trim().startsWith("•")) + .map((item, itemIndex) => { + const cleanItem = item.replace("• ", ""); + const parts = cleanItem.split(" – "); + return ( +
  • + {parts[0]} + {parts[1] && ` – ${parts[1]}`} +
  • + ); + })} +
+ ) : ( +

+ {paragraph} +

+ )} +
+ ))} +
+
+ )} + + {department.leadershipHeading && department.leadershipDescription && ( +
+

+ {department.leadershipHeading} +

+
+ {department.leadershipDescription + .split("\n\n") + .map((paragraph, index) => ( +
+ {paragraph.startsWith("•") ? ( +
    + {paragraph + .split("\n") + .filter((line) => line.trim().startsWith("•")) + .map((item, itemIndex) => { + const cleanItem = item.replace("• ", ""); + const parts = cleanItem.split(" – "); + return ( +
  • + {parts[0].match(/\*\*([^*]+)\*\*/) ? ( + $1", + ), + }} + /> + ) : ( + {parts[0]} + )} + {parts[1] && ` – ${parts[1]}`} +
  • + ); + })} +
+ ) : ( +

+ {paragraph.match(/\*\*([^*]+)\*\*/) || + paragraph.match(/\[([^\]]+)\]\(([^)]+)\)/) ? ( + $1", + ) + .replace( + /\[([^\]]+)\]\(([^)]+)\)/g, + '$1', + ), + }} + /> + ) : ( + {paragraph} + )} +

+ )} +
+ ))} +
+
+ )} + +
+

+ Other {jurisdiction.name} Government Ministries +

+ +
+
+
+
+ ); +} diff --git a/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx new file mode 100644 index 00000000..5d871937 --- /dev/null +++ b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx @@ -0,0 +1,93 @@ +import { JurisdictionPageContent } from "@/components/JurisdictionPageContent"; +import { initLingui } from "@/initLingui"; +import { + getExpandedDepartments, + getJurisdictionData, + getMunicipalitiesByProvince, + getAvailableYearsForJurisdiction, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import { notFound } from "next/navigation"; + +export const dynamicParams = false; + +export async function generateStaticParams() { + const municipalitiesByProvince = getMunicipalitiesByProvince(); + + const all = locales.flatMap((lang) => + municipalitiesByProvince.flatMap(({ province, municipalities }) => + municipalities.flatMap((municipality) => { + const jurisdictionSlug = `${province}/${municipality.slug}`; + const years = getAvailableYearsForJurisdiction(jurisdictionSlug); + return years.map((year) => ({ + lang, + province, + municipality: municipality.slug, + year, + })); + }), + ), + ); + + return all; +} + +type MunicipalYearPageContentProps = { + province: string; + municipality: string; + year: string; + lang: string; +}; + +export async function MunicipalYearPageContent({ + province, + municipality, + year, + lang, +}: MunicipalYearPageContentProps) { + initLingui(lang); + + const jurisdictionSlug = `${province}/${municipality}`; + + try { + const { jurisdiction, sankey } = getJurisdictionData( + jurisdictionSlug, + year, + ); + const departments = getExpandedDepartments(jurisdictionSlug, year); + + return ( + + ); + } catch { + notFound(); + } +} + +export default async function MunicipalYearPage({ + params, +}: { + params: Promise<{ + province: string; + municipality: string; + year: string; + lang: string; + }>; +}) { + const { province, municipality, year, lang } = await params; + return ( + + ); +} diff --git a/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx b/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx new file mode 100644 index 00000000..b8fa9a57 --- /dev/null +++ b/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx @@ -0,0 +1,82 @@ +import { MunicipalYearPageContent } from "@/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page"; +import { BASE_URL } from "@/lib/constants"; +import { + getMunicipalitiesByProvince, + getAvailableYearsForJurisdiction, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import { generateHreflangAlternates } from "@/lib/utils"; +import { notFound } from "next/navigation"; +import { Metadata } from "next"; + +export const dynamicParams = false; + +export async function generateStaticParams() { + const municipalitiesByProvince = getMunicipalitiesByProvince(); + + return locales.flatMap((lang) => + municipalitiesByProvince.flatMap(({ province, municipalities }) => + municipalities.map((municipality) => ({ + lang, + province, + municipality: municipality.slug, + })), + ), + ); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ province: string; municipality: string; lang: string }>; +}): Promise { + const { province, municipality, lang } = await params; + const jurisdictionSlug = `${province}/${municipality}`; + const years = getAvailableYearsForJurisdiction(jurisdictionSlug); + const latestYear = years.length > 0 ? years[0] : null; + + if (!latestYear) { + return {}; + } + + // Set canonical URL to the latest year's URL to avoid duplicate content SEO issues + const canonical = `${BASE_URL}/${lang}/municipal/${province}/${municipality}/${latestYear}`; + return { + alternates: { + ...generateHreflangAlternates( + lang, + `/municipal/[province]/[municipality]`, + { province, municipality }, + ), + canonical, + }, + }; +} + +export default async function MunicipalPage({ + params, +}: { + params: Promise<{ province: string; municipality: string; lang: string }>; +}) { + const { province, municipality, lang } = await params; + + const jurisdictionSlug = `${province}/${municipality}`; + + // Get the latest year for this municipality + const years = getAvailableYearsForJurisdiction(jurisdictionSlug); + const latestYear = years.length > 0 ? years[0] : null; + + if (!latestYear) { + notFound(); + } + + // Use the component from the year route + return ( + + ); +} diff --git a/src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx b/src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx similarity index 93% rename from src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx rename to src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx index 19dee5ec..73735354 100644 --- a/src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx +++ b/src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx @@ -3,27 +3,30 @@ import { getDepartmentsForJurisdiction, getExpandedDepartments, getJurisdictionData, - getJurisdictionSlugs, + getProvincialSlugs, + getAvailableYearsForJurisdiction, } from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; export const dynamicParams = false; export async function generateStaticParams() { - const jurisdictions = getJurisdictionSlugs(); - const languages = ["en", "fr"]; // or import from your locales + const provinces = getProvincialSlugs(); - const all = languages.flatMap((lang) => - jurisdictions.flatMap((jurisdiction) => { - const departments = getDepartmentsForJurisdiction(jurisdiction); - return departments.map((department) => ({ - lang, - jurisdiction, - department, - })); + return locales.flatMap((lang) => + provinces.flatMap((province) => { + const years = getAvailableYearsForJurisdiction(province); + return years.flatMap((year) => { + const departments = getDepartmentsForJurisdiction(province, year); + return departments.map((department) => ({ + lang, + province, + year, + department, + })); + }); }), ); - - return all; } import { @@ -45,20 +48,21 @@ import { JurisdictionDepartmentList } from "@/components/DepartmentList"; export default async function DepartmentPage({ params, }: { - params: Promise<{ lang: string; jurisdiction: string; department: string }>; + params: Promise<{ + lang: string; + province: string; + year: string; + department: string; + }>; }) { - const { - lang, - jurisdiction: jurisdictionSlug, - department: departmentSlug, - } = await params; + const { lang, province, year, department: departmentSlug } = await params; initLingui(lang); - const { jurisdiction } = getJurisdictionData(jurisdictionSlug); - const departments = getExpandedDepartments(jurisdiction.slug); + const { jurisdiction } = getJurisdictionData(province, year); + const departments = getExpandedDepartments(province, year); - const department = getDepartmentData(jurisdictionSlug, departmentSlug); + const department = getDepartmentData(province, departmentSlug, year); return ( @@ -400,6 +404,7 @@ export default async function DepartmentPage({ lang={lang} departments={departments} current={department.slug} + basePath={`/${lang}/provincial/${province}/${year}`} /> diff --git a/src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx b/src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx new file mode 100644 index 00000000..1713ca82 --- /dev/null +++ b/src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx @@ -0,0 +1,72 @@ +import { JurisdictionPageContent } from "@/components/JurisdictionPageContent"; +import { initLingui } from "@/initLingui"; +import { + getExpandedDepartments, + getJurisdictionData, + getProvincialSlugs, + getAvailableYearsForJurisdiction, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import { notFound } from "next/navigation"; + +export const dynamicParams = false; + +export async function generateStaticParams() { + const provinces = getProvincialSlugs(); + + const all = locales.flatMap((lang) => + provinces.flatMap((province) => { + const years = getAvailableYearsForJurisdiction(province); + return years.map((year) => ({ + lang, + province, + year, + })); + }), + ); + + return all; +} + +type ProvincialYearPageContentProps = { + province: string; + year: string; + lang: string; +}; + +export async function ProvincialYearPageContent({ + province, + year, + lang, +}: ProvincialYearPageContentProps) { + initLingui(lang); + + try { + const { jurisdiction, sankey } = getJurisdictionData(province, year); + const departments = getExpandedDepartments(province, year); + + return ( + + ); + } catch { + notFound(); + } +} + +export default async function ProvincialYearPage({ + params, +}: { + params: Promise<{ province: string; year: string; lang: string }>; +}) { + const { province, year, lang } = await params; + return ( + + ); +} diff --git a/src/app/[lang]/(main)/provincial/[province]/page.tsx b/src/app/[lang]/(main)/provincial/[province]/page.tsx new file mode 100644 index 00000000..68e87ab2 --- /dev/null +++ b/src/app/[lang]/(main)/provincial/[province]/page.tsx @@ -0,0 +1,74 @@ +import { ProvincialYearPageContent } from "@/app/[lang]/(main)/provincial/[province]/[year]/page"; +import { BASE_URL } from "@/lib/constants"; +import { + getProvincialSlugs, + getAvailableYearsForJurisdiction, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import { generateHreflangAlternates } from "@/lib/utils"; +import { notFound } from "next/navigation"; +import { Metadata } from "next"; + +export const dynamicParams = false; + +export async function generateStaticParams() { + const provinces = getProvincialSlugs(); + + return locales.flatMap((lang) => + provinces.map((province) => ({ + lang, + province, + })), + ); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ province: string; lang: string }>; +}): Promise { + const { province, lang } = await params; + const years = getAvailableYearsForJurisdiction(province); + const latestYear = years.length > 0 ? years[0] : null; + + if (!latestYear) { + return {}; + } + + // Set canonical URL to the latest year's URL to avoid duplicate content SEO issues + const canonical = `${BASE_URL}/${lang}/provincial/${province}/${latestYear}`; + + return { + alternates: { + ...generateHreflangAlternates(lang, `/provincial/[province]`, { + province, + }), + canonical, + }, + }; +} + +export default async function ProvincialPage({ + params, +}: { + params: Promise<{ province: string; lang: string }>; +}) { + const { province, lang } = await params; + + // Get the latest year for this province + const years = getAvailableYearsForJurisdiction(province); + const latestYear = years.length > 0 ? years[0] : null; + + if (!latestYear) { + notFound(); + } + + // Use the component from the year route + return ( + + ); +} diff --git a/src/app/[lang]/(main)/spending/page.tsx b/src/app/[lang]/(main)/spending/page.tsx index 0d2745e5..d774bca1 100644 --- a/src/app/[lang]/(main)/spending/page.tsx +++ b/src/app/[lang]/(main)/spending/page.tsx @@ -1,285 +1,17 @@ "use client"; -import { TenureChart } from "@/app/[lang]/(main)/spending/TenureChart"; -import { SalaryDistributionChart } from "@/app/[lang]/(main)/spending/SalaryDistributionChart"; -import { BarChart } from "@/components/BarChart"; -import { DepartmentList } from "@/components/DepartmentList"; -import { - ExternalLink, - H1, - H2, - InternalLink, - Intro, - P, - Page, - PageContent, - Section, -} from "@/components/Layout"; -import NoSSR from "@/components/NoSSR"; -import { Sankey } from "@/components/Sankey"; -import { Trans, useLingui } from "@lingui/react/macro"; +import { SpendingPageContent } from "@/app/[lang]/(main)/federal/spending/page"; +import { useLingui } from "@lingui/react/macro"; import { localizedPath } from "@/lib/utils"; -const StatBox = ({ - title, - value, - description, - growthPercentage, -}: { - title: React.ReactNode; - value: string; - description: React.ReactNode; - growthPercentage?: number; -}) => ( -
-
{title}
-
{value}
-
{description}
- {growthPercentage && ( -
0 ? "text-green-600" : "text-red-600"}`} - > - {growthPercentage > 0 ? "+" : ""} - {growthPercentage}% over the last 5 years -
- )} -
-); - -const ageData = [ - { name: "<20", Count: 1756 }, - { name: "20-24", Count: 33596 }, - { name: "25-29", Count: 78800 }, - { name: "30-34", Count: 89426 }, - { name: "35-39", Count: 187657 }, - { name: "40-44", Count: 216806 }, - { name: "45-49", Count: 216024 }, - { name: "50-54", Count: 190226 }, - { name: "55-59", Count: 146872 }, - { name: "60-64", Count: 84865 }, - { name: "65+", Count: 39494 }, -]; - -const calculateGrowthPercentage = (dataType: string) => { - const headcountData = [ - { Year: "2019", Value: 382107 }, - { Year: "2020", Value: 390798 }, - { Year: "2021", Value: 413424 }, - { Year: "2022", Value: 431739 }, - { Year: "2023", Value: 440985 }, - ]; - - const wagesData = [ - { Year: "2019", Value: 46.3 }, - { Year: "2020", Value: 53.0 }, - { Year: "2021", Value: 60.7 }, - { Year: "2022", Value: 56.5 }, - { Year: "2023", Value: 65.3 }, - ]; - - const compensationData = [ - { Year: "2019", Value: 117522 }, - { Year: "2020", Value: 123163 }, - { Year: "2021", Value: 125256 }, - { Year: "2022", Value: 126634 }, - { Year: "2023", Value: 136345 }, - ]; - - if (dataType === "headcount") { - const oldValue = headcountData[0].Value; - const newValue = headcountData[headcountData.length - 1].Value; - return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); - } else if (dataType === "wages") { - const oldValue = wagesData[0].Value; - const newValue = wagesData[wagesData.length - 1].Value; - return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); - } else if (dataType === "compensation") { - const oldValue = compensationData[0].Value; - const newValue = compensationData[compensationData.length - 1].Value; - return Number((((newValue - oldValue) / oldValue) * 100).toFixed(1)); - } - return 0; -}; - export default function Spending() { - const { t, i18n } = useLingui(); - return ( - - -
-

- Government Spending -

- - - Get data-driven insights into how governmental revenue and - spending affect Canadian lives and programs. - - -
-
-

- FY 2024 Government Revenue and Spending -

-

- - Explore revenue and spending categories or filter by agency for - deeper insights. - -

-
-
-
- - - -
- - Source - -
-
- - View this chart in full screen - -
-
- -
-

- Government Workforce -

-
- + const { i18n } = useLingui(); - - - - -
-

- Type of Tenure -

-

- 80% of employees are in permanent roles -

-
- - - -
-
- -
-

- Age -

-

- The average employee is 43.3 years old -

-
- - - Intl.NumberFormat("en-US", { - notation: "compact", - }).format(Number(value)) - } - /> - -
-
- - -

- Sources:{" "} - - PBO - - ,{" "} - - Treasury Board - -

- -
-

- Salary Distribution -

-

- - Explore federal employee salary distribution by year and - demographic group - -

- - - -
-

- Source:{" "} - - Treasury Board - -

-
-
-
-

- Government Departments explained -

- -
-
-

- Sources -

-

- - All government spending data is sourced from official databases, - but due to the complexity of these systems, occasional errors may - occur despite our best efforts. We aim to make this information - more accessible and accurate, and we welcome feedback. If you - notice any issues, please let us know{" "} - - here - {" "} - — we appreciate it and will work to address them promptly. - -

-
-
-
+ return ( + ); } diff --git a/src/app/[lang]/(main)/tax-visualizer/layout.tsx b/src/app/[lang]/(main)/tax-visualizer/layout.tsx new file mode 100644 index 00000000..4d8c164d --- /dev/null +++ b/src/app/[lang]/(main)/tax-visualizer/layout.tsx @@ -0,0 +1,28 @@ +import { generateHreflangAlternates } from "@/lib/utils"; +import { initLingui, PageLangParam } from "@/initLingui"; +import { useLingui } from "@lingui/react/macro"; +import { PropsWithChildren } from "react"; + +export async function generateMetadata( + props: PropsWithChildren, +) { + const lang = (await props.params).lang; + initLingui(lang); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const { t } = useLingui(); + + return { + title: t`Tax Visualizer | Canada Spends`, + description: t`See how your taxes are spent across federal and provincial programs. Interactive calculator for Canadian taxpayers.`, + alternates: generateHreflangAlternates(lang), + }; +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[lang]/(main)/tax-visualizer/page.tsx b/src/app/[lang]/(main)/tax-visualizer/page.tsx index b7b5f14d..80e7bf43 100644 --- a/src/app/[lang]/(main)/tax-visualizer/page.tsx +++ b/src/app/[lang]/(main)/tax-visualizer/page.tsx @@ -285,7 +285,7 @@ export default function TaxCalculatorPage() { For further breakdowns of spending, see{" "} Federal diff --git a/src/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx b/src/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx new file mode 100644 index 00000000..a1b430b1 --- /dev/null +++ b/src/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx @@ -0,0 +1,55 @@ +import { SpendingFullScreen } from "@/components/SpendingFullScreen"; +import { + getJurisdictionData, + getMunicipalitiesByProvince, + getAvailableYears, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import path from "path"; + +const dataDir = path.join(process.cwd(), "data"); + +export const dynamicParams = false; + +export async function generateStaticParams() { + const municipalitiesByProvince = getMunicipalitiesByProvince(); + + const all = locales.flatMap((lang) => + municipalitiesByProvince.flatMap(({ province, municipalities }) => + municipalities.flatMap((municipality) => { + const municipalPath = path.join( + dataDir, + "municipal", + province, + municipality.slug, + ); + const years = getAvailableYears(municipalPath); + return years.map((year) => ({ + lang, + province, + municipality: municipality.slug, + year, + })); + }), + ), + ); + + return all; +} + +export default async function FullPageSpending({ + params, +}: { + params: Promise<{ + province: string; + municipality: string; + year: string; + lang: string; + }>; +}) { + const { province, municipality, year } = await params; + const jurisdictionSlug = `${province}/${municipality}`; + const { jurisdiction, sankey } = getJurisdictionData(jurisdictionSlug, year); + + return ; +} diff --git a/src/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx b/src/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx new file mode 100644 index 00000000..8ba7890c --- /dev/null +++ b/src/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx @@ -0,0 +1,41 @@ +import { SpendingFullScreen } from "@/components/SpendingFullScreen"; +import { + getJurisdictionData, + getProvincialSlugs, + getAvailableYears, +} from "@/lib/jurisdictions"; +import { locales } from "@/lib/constants"; +import path from "path"; + +const dataDir = path.join(process.cwd(), "data"); + +export const dynamicParams = false; + +export async function generateStaticParams() { + const provinces = getProvincialSlugs(); + + const all = locales.flatMap((lang) => + provinces.flatMap((province) => { + const provincialPath = path.join(dataDir, "provincial", province); + const years = getAvailableYears(provincialPath); + return years.map((year) => ({ + lang, + province, + year, + })); + }), + ); + + return all; +} + +export default async function FullPageSpending({ + params, +}: { + params: Promise<{ province: string; year: string; lang: string }>; +}) { + const { province, year } = await params; + const { jurisdiction, sankey } = getJurisdictionData(province, year); + + return ; +} diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx index b7237b3f..a6071153 100644 --- a/src/app/[lang]/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -1,14 +1,7 @@ -import { allMessages } from "@/appRouterI18n"; -import { LinguiClientProvider } from "@/components/LinguiClientProvider"; import { initLingui, PageLangParam } from "@/initLingui"; -import { cn, generateHreflangAlternates } from "@/lib/utils"; +import { generateHreflangAlternates } from "@/lib/utils"; import { useLingui } from "@lingui/react/macro"; -import { Analytics } from "@vercel/analytics/next"; -import { Plus_Jakarta_Sans } from "next/font/google"; import { PropsWithChildren } from "react"; -import { Toaster } from "sonner"; -import "./globals.css"; -import { PostHogProvider } from "./providers"; export async function generateMetadata( props: PropsWithChildren, @@ -60,45 +53,13 @@ export async function generateMetadata( }; } -const plusJakartaSans = Plus_Jakarta_Sans({ - weight: ["600", "700"], - subsets: ["latin"], -}); - -export default async function RootLayout({ +export default async function LangLayout({ children, params, }: PropsWithChildren) { const lang = (await params).lang; initLingui(lang); - return ( - - - - - {children} - - - - - {/* Simple Analytics Script */} - - - - - ); + // Root layout provides and , so this nested layout just wraps content + return <>{children}; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 00000000..ba59e775 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,53 @@ +import { allMessages } from "@/appRouterI18n"; +import { LinguiClientProvider } from "@/components/LinguiClientProvider"; +import { initLingui } from "@/initLingui"; +import { cn } from "@/lib/utils"; +import { Analytics } from "@vercel/analytics/next"; +import { Plus_Jakarta_Sans } from "next/font/google"; +import { ReactNode } from "react"; +import { Toaster } from "sonner"; +import "./[lang]/globals.css"; +import { PostHogProvider } from "./[lang]/providers"; + +// Root layout must provide and tags +// Default to 'en' for root layout (actual lang is handled by [lang]/layout) +const plusJakartaSans = Plus_Jakarta_Sans({ + weight: ["600", "700"], + subsets: ["latin"], +}); + +export default function RootLayout({ children }: { children: ReactNode }) { + // Initialize with default language for root layout + // The [lang] layout will override this with the actual language + initLingui("en"); + + return ( + + + + + {children} + + + + + {/* Simple Analytics Script */} + + + + + ); +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 096b707a..7f820d3f 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,36 +1,13 @@ import { MetadataRoute } from "next"; import { BASE_URL, locales } from "@/lib/constants"; import path from "path"; -import fs from "fs"; - -interface StaticData { - fileStats: Record; - structure: { - provincial: Record; - municipal: Record>; - articles: Record; - }; -} - -// Load static data once at module level -const staticData = ((): StaticData | null => { - const dataPath = path.join(process.cwd(), "data", "static-data.json"); - try { - if (fs.existsSync(dataPath)) { - return JSON.parse(fs.readFileSync(dataPath, "utf8")) as StaticData; - } - } catch (error) { - console.warn("Failed to load static data:", error); - } - return null; -})(); - -const getFileLastModified = (filePath: string): Date | undefined => { - // Convert absolute path to relative path for lookup - const relativePath = path.relative(process.cwd(), filePath); - const isoString = staticData?.fileStats[relativePath]; - return isoString ? new Date(isoString) : undefined; -}; +import { + getProvincialSlugs, + getMunicipalitiesByProvince, + getAvailableYearsForJurisdiction, + getFileLastModified, +} from "@/lib/jurisdictions"; +import { getArticleSlugs } from "@/lib/articles"; export default function sitemap(): MetadataRoute.Sitemap { const urls: MetadataRoute.Sitemap = []; @@ -38,12 +15,12 @@ export default function sitemap(): MetadataRoute.Sitemap { const addUrl = ( url: string, changeFreq: MetadataRoute.Sitemap[number]["changeFrequency"] = "yearly", - dataFilePath?: string, + relativeFilePath?: string, ) => { urls.push({ url: `${BASE_URL}${url}`, - lastModified: dataFilePath - ? getFileLastModified(dataFilePath) + lastModified: relativeFilePath + ? getFileLastModified(relativeFilePath) : new Date(), changeFrequency: changeFreq, }); @@ -61,62 +38,82 @@ export default function sitemap(): MetadataRoute.Sitemap { addUrl(`/${lang}/articles`); // Articles - for (const slug of staticData?.structure.articles[lang] || []) { - const articlePath = path.join( - process.cwd(), - "articles", - lang, - slug, - "metadata.json", - ); + for (const slug of getArticleSlugs(lang)) { + const articlePath = path.join("articles", lang, slug, "metadata.json"); addUrl(`/${lang}/articles/${slug}`, "yearly", articlePath); } } - // Provincial and municipal pages (non-year only) - if (staticData) { - const dataDir = path.join(process.cwd(), "data"); + // Provincial and municipal pages (non-year and year-specific) + for (const lang of locales) { + // Provincial + for (const province of getProvincialSlugs()) { + const years = getAvailableYearsForJurisdiction(province); + if (years.length === 0) continue; - for (const lang of locales) { - // Provincial - for (const [province, { years }] of Object.entries( - staticData.structure.provincial, - )) { + // Add non-year URL (renders latest year) + const latestYear = years[0]; + const latestYearFilePath = latestYear + ? path.join("data", "provincial", province, latestYear, "summary.json") + : undefined; + addUrl(`/${lang}/provincial/${province}`, "yearly", latestYearFilePath); + + // Add year-specific URLs + for (const year of years) { + const yearFilePath = path.join( + "data", + "provincial", + province, + year, + "summary.json", + ); + addUrl( + `/${lang}/provincial/${province}/${year}`, + "yearly", + yearFilePath, + ); + } + } + + // Municipal + for (const { province, municipalities } of getMunicipalitiesByProvince()) { + for (const municipality of municipalities) { + const jurisdiction = `${province}/${municipality.slug}`; + const years = getAvailableYearsForJurisdiction(jurisdiction); + if (years.length === 0) continue; + + // Add non-year URL (renders latest year) const latestYear = years[0]; - const filePath = latestYear + const latestYearFilePath = latestYear ? path.join( - dataDir, - "provincial", + "data", + "municipal", province, + municipality.slug, latestYear, "summary.json", ) : undefined; - addUrl(`/${lang}/provincial/${province}`, "yearly", filePath); - } + addUrl( + `/${lang}/municipal/${province}/${municipality.slug}`, + "yearly", + latestYearFilePath, + ); - // Municipal - for (const [province, municipalities] of Object.entries( - staticData.structure.municipal, - )) { - for (const [municipality, { years }] of Object.entries( - municipalities, - )) { - const latestYear = years[0]; - const filePath = latestYear - ? path.join( - dataDir, - "municipal", - province, - municipality, - latestYear, - "summary.json", - ) - : undefined; + // Add year-specific URLs + for (const year of years) { + const yearFilePath = path.join( + "data", + "municipal", + province, + municipality.slug, + year, + "summary.json", + ); addUrl( - `/${lang}/municipal/${province}/${municipality}`, + `/${lang}/municipal/${province}/${municipality.slug}/${year}`, "yearly", - filePath, + yearFilePath, ); } } diff --git a/src/components/DepartmentList.tsx b/src/components/DepartmentList.tsx index 39c59736..9352a304 100644 --- a/src/components/DepartmentList.tsx +++ b/src/components/DepartmentList.tsx @@ -16,7 +16,7 @@ const DepartmentItem = ({ name, slug }: DepartmentProps) => { return (
{name} @@ -52,19 +52,22 @@ interface JurisdictionDepartmentProps { jurisdiction: Jurisdiction; department: Department; lang: string; + basePath?: string; } const JurisdictionDepartmentItem = ({ lang, jurisdiction, department, + basePath, }: JurisdictionDepartmentProps) => { + const href = basePath + ? `${basePath}/departments/${department.slug}` + : `/${lang}/${jurisdiction.slug}/${department.slug}`; + return (
- + {department.name}
@@ -76,6 +79,7 @@ export const JurisdictionDepartmentList = (props: { jurisdiction: Jurisdiction; departments: Department[]; current?: string; + basePath?: string; }) => { const BrowsableDepartment = props.departments .filter((d) => !!d.slug && !!d.name) @@ -91,6 +95,7 @@ export const JurisdictionDepartmentList = (props: { lang={props.lang} jurisdiction={props.jurisdiction} department={department} + basePath={props.basePath} /> ))}
diff --git a/src/app/[lang]/(main)/[jurisdiction]/page.tsx b/src/components/JurisdictionPageContent.tsx similarity index 94% rename from src/app/[lang]/(main)/[jurisdiction]/page.tsx rename to src/components/JurisdictionPageContent.tsx index fcfe812c..7c050537 100644 --- a/src/app/[lang]/(main)/[jurisdiction]/page.tsx +++ b/src/components/JurisdictionPageContent.tsx @@ -12,12 +12,8 @@ import { } from "@/components/Layout"; import { JurisdictionSankey } from "@/components/Sankey/JurisdictionSankey"; import { Tooltip } from "@/components/Tooltip"; -import { initLingui } from "@/initLingui"; -import { - getExpandedDepartments, - getJurisdictionData, - getJurisdictionSlugs, -} from "@/lib/jurisdictions"; +import { Jurisdiction, Department } from "@/lib/jurisdictions"; +import { SankeyData } from "@/components/Sankey/SankeyChartD3"; import { localizedPath } from "@/lib/utils"; import { Trans } from "@lingui/react/macro"; @@ -46,31 +42,23 @@ type SankeyNode = { children?: SankeyNode[]; }; -export const dynamicParams = false; - -export function generateStaticParams() { - const jurisdictions = getJurisdictionSlugs(); - const languages = ["en", "fr"]; // or import { locales } from '@/locales'; - - const all = languages.flatMap((lang) => - jurisdictions.map((jurisdiction) => ({ lang, jurisdiction })), - ); - - return all; +interface JurisdictionPageContentProps { + jurisdiction: Jurisdiction; + sankey: SankeyData; + departments: Department[]; + lang: string; + basePath: string; + fullScreenPath: string; } -export default async function ProvinceIndex({ - params, -}: { - params: Promise<{ jurisdiction: string; lang: string }>; -}) { - const { jurisdiction: slug, lang } = await params; - initLingui(lang); - - const { jurisdiction, sankey } = getJurisdictionData(slug); - - const departments = getExpandedDepartments(jurisdiction.slug); - +export function JurisdictionPageContent({ + jurisdiction, + sankey, + departments, + lang, + basePath, + fullScreenPath, +}: JurisdictionPageContentProps) { const ministriesArray = (jurisdiction as { ministries?: unknown[] }) .ministries; const ministriesCount = Array.isArray(ministriesArray) @@ -330,10 +318,7 @@ export default async function ProvinceIndex({
View this chart in full screen @@ -394,6 +379,7 @@ export default async function ProvinceIndex({ jurisdiction={jurisdiction} lang={lang} departments={departments} + basePath={basePath} /> )} diff --git a/src/components/MainLayout/index.tsx b/src/components/MainLayout/index.tsx index e82b59f0..aba5190f 100644 --- a/src/components/MainLayout/index.tsx +++ b/src/components/MainLayout/index.tsx @@ -72,8 +72,10 @@ export const MainLayout = ({ pathSegments[0] === i18n.locale ? pathSegments[1] : pathSegments[0]; const spendingActive = - pathname.startsWith(`/${i18n.locale}/spending`) || - pathname.startsWith(`/${i18n.locale}/budget`) || + pathname.startsWith(`/${i18n.locale}/federal/spending`) || + pathname.startsWith(`/${i18n.locale}/federal/budget`) || + pathname.startsWith(`/${i18n.locale}/provincial/`) || + pathname.startsWith(`/${i18n.locale}/municipal/`) || (firstSegment ? jurisdictionSlugsSet.has(firstSegment) : false); return ( @@ -119,7 +121,7 @@ export const MainLayout = ({ > Federal @@ -128,7 +130,7 @@ export const MainLayout = ({ Budget @@ -148,7 +150,7 @@ export const MainLayout = ({ {provinces.map((provinceSlug) => ( {provinceNames[provinceSlug]} @@ -187,7 +189,7 @@ export const MainLayout = ({ asChild > {municipality.name} @@ -294,8 +296,8 @@ export const MainLayout = ({ Government Spending

setIsMenuOpen(false)} > @@ -304,8 +306,8 @@ export const MainLayout = ({ setIsMenuOpen(false)} > @@ -320,8 +322,10 @@ export const MainLayout = ({ {provinces.map((provinceSlug) => ( setIsMenuOpen(false)} > @@ -342,9 +346,9 @@ export const MainLayout = ({ {municipalities.map((municipality) => ( setIsMenuOpen(false)} > diff --git a/src/components/Sankey/SankeyChart.tsx b/src/components/Sankey/SankeyChart.tsx index ee0f016b..efb00e6f 100644 --- a/src/components/Sankey/SankeyChart.tsx +++ b/src/components/Sankey/SankeyChart.tsx @@ -287,7 +287,7 @@ export function SankeyChart(props: SankeyChartProps) { {hoverNode.departmentSlug && props.showDepartmentLinks && (
Learn more diff --git a/src/app/[lang]/(mobile)/[jurisdiction]/spending-full-screen/page.tsx b/src/components/SpendingFullScreen.tsx similarity index 59% rename from src/app/[lang]/(mobile)/[jurisdiction]/spending-full-screen/page.tsx rename to src/components/SpendingFullScreen.tsx index 46b9b937..626101a7 100644 --- a/src/app/[lang]/(mobile)/[jurisdiction]/spending-full-screen/page.tsx +++ b/src/components/SpendingFullScreen.tsx @@ -1,21 +1,17 @@ import { ExternalLink } from "@/components/Layout"; import { JurisdictionSankey } from "@/components/Sankey/JurisdictionSankey"; -import { getJurisdictionData, getJurisdictionSlugs } from "@/lib/jurisdictions"; +import { Jurisdiction } from "@/lib/jurisdictions"; +import { SankeyData } from "@/components/Sankey/SankeyChartD3"; -export function generateStaticParams() { - const slugs = getJurisdictionSlugs(); - - return slugs.map((slug) => ({ jurisdiction: slug })); +interface SpendingFullScreenProps { + jurisdiction: Jurisdiction; + sankey: SankeyData; } -export default async function FullPageSpending({ - params, -}: { - params: Promise<{ jurisdiction: string }>; -}) { - const { jurisdiction: slug } = await params; - const { jurisdiction, sankey } = getJurisdictionData(slug); - +export function SpendingFullScreen({ + jurisdiction, + sankey, +}: SpendingFullScreenProps) { return (
diff --git a/src/hooks/useDepartments.ts b/src/hooks/useDepartments.ts index 1113427f..f4d773d1 100644 --- a/src/hooks/useDepartments.ts +++ b/src/hooks/useDepartments.ts @@ -7,85 +7,85 @@ export const useDepartments = () => { { name: t`Finance Canada`, slug: "department-of-finance", - href: `/${i18n.locale}/spending/department-of-finance`, + href: `/${i18n.locale}/federal/spending/department-of-finance`, Percentage: 26.48, }, { name: t`Employment and Social Development Canada`, slug: "employment-and-social-development-canada", - href: `/${i18n.locale}/spending/employment-and-social-development-canada`, + href: `/${i18n.locale}/federal/spending/employment-and-social-development-canada`, Percentage: 18.38, }, { name: t`Indigenous Services Canada + Crown-Indigenous Relations and Northern Affairs Canada`, slug: "indigenous-services-and-northern-affairs", - href: `/${i18n.locale}/spending/indigenous-services-and-northern-affairs`, + href: `/${i18n.locale}/federal/spending/indigenous-services-and-northern-affairs`, Percentage: 12.25, }, { name: t`National Defence`, slug: "national-defence", - href: `/${i18n.locale}/spending/national-defence`, + href: `/${i18n.locale}/federal/spending/national-defence`, Percentage: 6.71, }, { name: t`Global Affairs Canada`, slug: "global-affairs-canada", - href: `/${i18n.locale}/spending/global-affairs-canada`, + href: `/${i18n.locale}/federal/spending/global-affairs-canada`, Percentage: 3.74, }, { name: t`Canada Revenue Agency`, slug: "canada-revenue-agency", - href: `/${i18n.locale}/spending/canada-revenue-agency`, + href: `/${i18n.locale}/federal/spending/canada-revenue-agency`, Percentage: 3.27, }, { name: t`Housing, Infrastructure and Communities Canada`, slug: "housing-infrastructure-communities", - href: `/${i18n.locale}/spending/housing-infrastructure-communities`, + href: `/${i18n.locale}/federal/spending/housing-infrastructure-communities`, Percentage: 2.82, }, { name: t`Public Safety Canada`, slug: "public-safety-canada", - href: `/${i18n.locale}/spending/public-safety-canada`, + href: `/${i18n.locale}/federal/spending/public-safety-canada`, Percentage: 2.71, }, { name: t`Health Canada`, slug: "health-canada", - href: `/${i18n.locale}/spending/health-canada`, + href: `/${i18n.locale}/federal/spending/health-canada`, Percentage: 2.67, }, { name: t`Innovation, Science and Industry`, slug: "innovation-science-and-industry", - href: `/${i18n.locale}/spending/innovation-science-and-industry`, + href: `/${i18n.locale}/federal/spending/innovation-science-and-industry`, Percentage: 2.0, }, { name: t`Public Services and Procurement Canada`, slug: "public-services-and-procurement-canada", - href: `/${i18n.locale}/spending/public-services-and-procurement-canada`, + href: `/${i18n.locale}/federal/spending/public-services-and-procurement-canada`, Percentage: 1.6, }, { name: t`Immigration, Refugees and Citizenship`, slug: "immigration-refugees-and-citizenship", - href: `/${i18n.locale}/spending/immigration-refugees-and-citizenship`, + href: `/${i18n.locale}/federal/spending/immigration-refugees-and-citizenship`, Percentage: 1.2, }, { name: t`Veterans Affairs`, slug: "veterans-affairs", - href: `/${i18n.locale}/spending/veterans-affairs`, + href: `/${i18n.locale}/federal/spending/veterans-affairs`, Percentage: 1.2, }, { name: t`Transport Canada`, slug: "transport-canada", - href: `/${i18n.locale}/spending/transport-canada`, + href: `/${i18n.locale}/federal/spending/transport-canada`, Percentage: 1.0, }, ]; diff --git a/src/lib/jurisdictions.ts b/src/lib/jurisdictions.ts index eb7255a8..f31923a1 100644 --- a/src/lib/jurisdictions.ts +++ b/src/lib/jurisdictions.ts @@ -5,33 +5,79 @@ import { provinceNames } from "./provinceNames"; const dataDir = path.join(process.cwd(), "data"); +// ============================================================================ +// STATIC DATA LOADING (Internal) +// ============================================================================ + /** - * Find the latest year folder in a jurisdiction directory that contains summary.json. - * Returns the year string (e.g., "2024") or null if no valid year folders found. + * Type definition for the static data structure */ -function findLatestYear(jurisdictionPath: string): string | null { - if (!fs.existsSync(jurisdictionPath)) { - return null; - } +type StaticData = { + _disclaimer: string; + fileStats: Record; + structure: { + provincial: Record< + string, + { years: string[]; departmentsByYear: Record } + >; + municipal: Record< + string, + Record< + string, + { years: string[]; departmentsByYear: Record } + > + >; + articles: { + en: string[]; + fr: string[]; + }; + }; +}; - const entries = fs.readdirSync(jurisdictionPath); - const yearFolders = entries - .filter((entry) => { - const fullPath = path.join(jurisdictionPath, entry); - return ( - fs.statSync(fullPath).isDirectory() && - /^\d{4}$/.test(entry) && // Matches 4-digit year folders - fs.existsSync(path.join(fullPath, "summary.json")) // Must have summary.json - ); - }) - .map((year) => parseInt(year, 10)) - .sort((a, b) => b - a); // Sort descending (latest first) +/** + * Cached static data to avoid repeated file reads + */ +let cachedStaticData: StaticData | null = null; - if (yearFolders.length === 0) { - return null; +/** + * Load and cache static data from data/static-data.json + * Internal function - consumers should use the public API functions below + */ +function loadStaticData(): StaticData { + if (!cachedStaticData) { + const staticDataPath = path.join(process.cwd(), "data", "static-data.json"); + if (fs.existsSync(staticDataPath)) { + cachedStaticData = JSON.parse( + fs.readFileSync(staticDataPath, "utf8"), + ) as StaticData; + } else { + // Fallback to empty structure if file doesn't exist + cachedStaticData = { + _disclaimer: "", + fileStats: {}, + structure: { + provincial: {}, + municipal: {}, + articles: { en: [], fr: [] }, + }, + }; + } } + return cachedStaticData; +} + +// ============================================================================ +// PUBLIC API - JURISDICTION QUERIES +// ============================================================================ - return yearFolders[0].toString(); +/** + * Find the latest year folder in a jurisdiction directory that contains summary.json. + * Uses static-data.json for faster performance. + * Returns the year string (e.g., "2024") or null if no valid year folders found. + */ +function findLatestYear(jurisdictionPath: string): string | null { + const years = getAvailableYears(jurisdictionPath); + return years.length > 0 ? years[0] : null; } /** @@ -48,45 +94,92 @@ function getJurisdictionDataPath(jurisdictionPath: string): string { } /** - * Find the data path for a jurisdiction slug. - * Handles both provincial and municipal jurisdictions, with optional explicit province. + * Get available years for a jurisdiction path. + * Uses static-data.json for faster performance instead of filesystem lookups. + * Returns an array of year strings (e.g., ["2024", "2023"]) sorted descending. + * + * @internal Used by generateStaticParams in page components + */ +export function getAvailableYears(jurisdictionPath: string): string[] { + const staticData = loadStaticData(); + + // Parse the jurisdiction path to extract province/municipality + // Path format: data/provincial/{province} or data/municipal/{province}/{municipality} + const normalizedPath = path.normalize(jurisdictionPath); + const pathParts = normalizedPath.split(path.sep); + + // Find the index of "provincial" or "municipal" in the path + const provincialIndex = pathParts.indexOf("provincial"); + const municipalIndex = pathParts.indexOf("municipal"); + + if (provincialIndex !== -1 && provincialIndex + 1 < pathParts.length) { + // Provincial jurisdiction + const province = pathParts[provincialIndex + 1]; + const provinceData = staticData.structure.provincial[province]; + if (provinceData?.years) { + // Return years sorted descending (they should already be sorted, but ensure it) + return [...provinceData.years].sort( + (a, b) => parseInt(b, 10) - parseInt(a, 10), + ); + } + } else if (municipalIndex !== -1 && municipalIndex + 2 < pathParts.length) { + // Municipal jurisdiction + const province = pathParts[municipalIndex + 1]; + const municipality = pathParts[municipalIndex + 2]; + const municipalityData = + staticData.structure.municipal[province]?.[municipality]; + if (municipalityData?.years) { + // Return years sorted descending + return [...municipalityData.years].sort( + (a, b) => parseInt(b, 10) - parseInt(a, 10), + ); + } + } + + // Fallback to empty array if not found in static data + return []; +} + +/** + * Find the data path for a jurisdiction slug with optional year. * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) + * @param year - Optional year string (e.g., "2024") or null for latest * @returns The data path to the jurisdiction's data folder, or null if not found */ -function findJurisdictionDataPath(jurisdiction: string): string | null { +function findJurisdictionDataPathWithYear( + jurisdiction: string, + year: string | null = null, +): string | null { const parts = jurisdiction.split("/"); + let basePath: string | null = null; if (parts.length === 1) { // Could be a province or a municipality - check both const provincialPath = path.join(dataDir, "provincial", jurisdiction); - const provincialDataPath = getJurisdictionDataPath(provincialPath); - const provincialSummaryPath = path.join(provincialDataPath, "summary.json"); - - if (fs.existsSync(provincialSummaryPath)) { - return provincialDataPath; - } - - // It's likely a municipality - search for it - const municipalDir = path.join(dataDir, "municipal"); - if (!fs.existsSync(municipalDir)) { - return null; - } - - const provinces = fs.readdirSync(municipalDir).filter((f) => { - const provincePath = path.join(municipalDir, f); - return fs.statSync(provincePath).isDirectory(); - }); - - for (const province of provinces) { - const municipalityPath = path.join(municipalDir, province, jurisdiction); - const municipalityDataPath = getJurisdictionDataPath(municipalityPath); - const summaryPath = path.join(municipalityDataPath, "summary.json"); - if (fs.existsSync(summaryPath)) { - return municipalityDataPath; + if (fs.existsSync(provincialPath)) { + basePath = provincialPath; + } else { + // It's likely a municipality - search for it + const municipalDir = path.join(dataDir, "municipal"); + if (fs.existsSync(municipalDir)) { + const provinces = fs.readdirSync(municipalDir).filter((f) => { + const provincePath = path.join(municipalDir, f); + return fs.statSync(provincePath).isDirectory(); + }); + + for (const province of provinces) { + const municipalityPath = path.join( + municipalDir, + province, + jurisdiction, + ); + if (fs.existsSync(municipalityPath)) { + basePath = municipalityPath; + break; + } + } } } - - return null; } else if (parts.length === 2) { // Municipal jurisdiction with explicit province const [province, municipality] = parts; @@ -96,15 +189,82 @@ function findJurisdictionDataPath(jurisdiction: string): string | null { province, municipality, ); - const municipalityDataPath = getJurisdictionDataPath(municipalityPath); - const summaryPath = path.join(municipalityDataPath, "summary.json"); - if (fs.existsSync(summaryPath)) { - return municipalityDataPath; + if (fs.existsSync(municipalityPath)) { + basePath = municipalityPath; + } + } + + if (!basePath) { + return null; + } + + // If year is specified, use it; otherwise use latest year + if (year) { + const yearPath = path.join(basePath, year); + if (fs.existsSync(path.join(yearPath, "summary.json"))) { + return yearPath; } return null; } - return null; + // Use latest year + return getJurisdictionDataPath(basePath); +} + +/** + * Get available years for a jurisdiction slug. + * Uses static-data.json for faster performance. + * Returns an array of year strings (e.g., ["2024", "2023"]) sorted descending. + * + * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) + * @returns Array of year strings, sorted descending, or empty array if not found + */ +export function getAvailableYearsForJurisdiction( + jurisdiction: string, +): string[] { + const staticData = loadStaticData(); + const parts = jurisdiction.split("/"); + + if (parts.length === 1) { + // Check if it's a province + const provinceData = staticData.structure.provincial[jurisdiction]; + if (provinceData?.years?.length > 0) { + // Years are already sorted descending in static data + return [...provinceData.years]; + } + + // Check if it's a municipality (search across all provinces) + for (const province of Object.keys(staticData.structure.municipal)) { + const municipalityData = + staticData.structure.municipal[province]?.[jurisdiction]; + if (municipalityData?.years?.length > 0) { + return [...municipalityData.years]; + } + } + } else if (parts.length === 2) { + // Municipal jurisdiction with explicit province + const [province, municipality] = parts; + const municipalityData = + staticData.structure.municipal[province]?.[municipality]; + if (municipalityData?.years?.length > 0) { + return [...municipalityData.years]; + } + } + + return []; +} + +/** + * Get the latest year for a jurisdiction. + * Uses static-data.json for faster performance. + * + * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) + * @returns The latest year string or null if not found + * @internal Used internally by non-year jurisdiction pages + */ +function getLatestYearForJurisdiction(jurisdiction: string): string | null { + const years = getAvailableYearsForJurisdiction(jurisdiction); + return years.length > 0 ? years[0] : null; } export type Jurisdiction = { @@ -165,61 +325,56 @@ type Data = { }; /** - * Get provincial jurisdiction slugs (provinces with provincial-level data). + * Get all provincial jurisdiction slugs. + * Returns provinces that have data available, sorted alphabetically. + * Uses static-data.json internally for faster performance. + * + * @example + * const provinces = getProvincialSlugs(); + * // ["alberta", "british-columbia", "ontario"] */ export function getProvincialSlugs(): string[] { - const provincialDir = path.join(dataDir, "provincial"); - if (!fs.existsSync(provincialDir)) { - return []; - } - return fs.readdirSync(provincialDir).filter((f) => { - const fullPath = path.join(provincialDir, f); - if (!fs.statSync(fullPath).isDirectory()) { - return false; - } - const dataPath = getJurisdictionDataPath(fullPath); - return fs.existsSync(path.join(dataPath, "summary.json")); - }); + const staticData = loadStaticData(); + return Object.keys(staticData.structure.provincial).sort(); } /** - * Get municipalities grouped by province. - * Returns array of { province: string, municipalities: Array<{ slug: string, name: string }> } + * Get all municipalities grouped by their province. + * Each municipality includes its slug and display name. + * Uses static-data.json internally for structure, reads summary.json for names. + * + * @returns Array of provinces with their municipalities + * @example + * const data = getMunicipalitiesByProvince(); + * // [ + * // { province: "ontario", municipalities: [{ slug: "toronto", name: "Toronto" }] }, + * // { province: "british-columbia", municipalities: [{ slug: "vancouver", name: "Vancouver" }] } + * // ] */ export function getMunicipalitiesByProvince(): Array<{ province: string; municipalities: Array<{ slug: string; name: string }>; }> { - const municipalDir = path.join(dataDir, "municipal"); - if (!fs.existsSync(municipalDir)) { - return []; - } - - const provinces = fs.readdirSync(municipalDir).filter((f) => { - const provincePath = path.join(municipalDir, f); - return fs.statSync(provincePath).isDirectory(); - }); - + const staticData = loadStaticData(); const result: Array<{ province: string; municipalities: Array<{ slug: string; name: string }>; }> = []; - for (const province of provinces) { - const provincePath = path.join(municipalDir, province); - const municipalities = fs - .readdirSync(provincePath) - .filter((f) => { - const municipalityPath = path.join(provincePath, f); - if (!fs.statSync(municipalityPath).isDirectory()) { - return false; - } - const dataPath = getJurisdictionDataPath(municipalityPath); - return fs.existsSync(path.join(dataPath, "summary.json")); - }) + for (const province of Object.keys(staticData.structure.municipal)) { + const municipalitySlugs = Object.keys( + staticData.structure.municipal[province] || {}, + ); + + const municipalities = municipalitySlugs .map((slug) => { // Try to get the name from summary.json, fallback to slug - const municipalityPath = path.join(provincePath, slug); + const municipalityPath = path.join( + dataDir, + "municipal", + province, + slug, + ); const dataPath = getJurisdictionDataPath(municipalityPath); const summaryPath = path.join(dataPath, "summary.json"); let name = slug; @@ -231,7 +386,7 @@ export function getMunicipalitiesByProvince(): Array<{ } return { slug, name }; }) - .sort((a, b) => a.name.localeCompare(b.name)); // Sort municipalities alphabetically + .sort((a, b) => a.name.localeCompare(b.name)); if (municipalities.length > 0) { result.push({ province, municipalities }); @@ -246,57 +401,23 @@ export function getMunicipalitiesByProvince(): Array<{ }); } -/** - * Get all jurisdiction slugs, including both provincial and municipal jurisdictions. - * Returns slugs in format "province" for provincial or just "municipality" for municipal - * (municipalities use simple slugs, not nested paths, to match URL structure) - */ -export function getJurisdictionSlugs(): string[] { - const slugs: string[] = []; - - // Get provincial jurisdictions - slugs.push(...getProvincialSlugs()); - - // Get municipal jurisdictions (return just municipality name, not nested path) - const municipalDir = path.join(dataDir, "municipal"); - if (fs.existsSync(municipalDir)) { - const provinces = fs.readdirSync(municipalDir).filter((f) => { - const provincePath = path.join(municipalDir, f); - return fs.statSync(provincePath).isDirectory(); - }); - - for (const province of provinces) { - const provincePath = path.join(municipalDir, province); - const municipalities = fs.readdirSync(provincePath).filter((f) => { - const municipalityPath = path.join(provincePath, f); - if (!fs.statSync(municipalityPath).isDirectory()) { - return false; - } - const dataPath = getJurisdictionDataPath(municipalityPath); - return fs.existsSync(path.join(dataPath, "summary.json")); - }); - - // Return just the municipality slug, not the nested path - for (const municipality of municipalities) { - slugs.push(municipality); - } - } - } - - return slugs; -} - /** * Get jurisdiction data, supporting both provincial and municipal paths. * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) + * @param year - Optional year string (e.g., "2024") or null for latest * @throws Error if jurisdiction data is not found */ -export function getJurisdictionData(jurisdiction: string): Data { +export function getJurisdictionData( + jurisdiction: string, + year: string | null = null, +): Data { const parts = jurisdiction.split("/"); - const jurisdictionPath = findJurisdictionDataPath(jurisdiction); + const jurisdictionPath = findJurisdictionDataPathWithYear(jurisdiction, year); if (!jurisdictionPath) { - throw new Error(`Jurisdiction data not found: ${jurisdiction}`); + throw new Error( + `Jurisdiction data not found: ${jurisdiction}${year ? ` for year ${year}` : ""}`, + ); } const summaryPath = path.join(jurisdictionPath, "summary.json"); @@ -329,13 +450,15 @@ export function getJurisdictionData(jurisdiction: string): Data { * Get department data for a specific jurisdiction and department. * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) * @param department - Department slug + * @param year - Optional year string (e.g., "2024") or null for latest * @throws Error if jurisdiction or department data is not found */ export function getDepartmentData( jurisdiction: string, department: string, + year: string | null = null, ): Department { - const jurisdictionPath = findJurisdictionDataPath(jurisdiction); + const jurisdictionPath = findJurisdictionDataPathWithYear(jurisdiction, year); if (!jurisdictionPath) { throw new Error(`Jurisdiction data not found: ${jurisdiction}`); @@ -371,13 +494,56 @@ export function getDepartmentData( /** * Get list of department slugs for a jurisdiction. + * Uses static-data.json for faster performance when year is provided. * Returns empty array if jurisdiction not found or has no departments (non-throwing). * @param jurisdiction - Slug in format "province" (provincial), "province/municipality" (municipal), or just "municipality" (will search) + * @param year - Optional year string (e.g., "2024") or null for latest * @returns Array of department slugs, or empty array if none found */ -export function getDepartmentsForJurisdiction(jurisdiction: string): string[] { - const jurisdictionPath = findJurisdictionDataPath(jurisdiction); +export function getDepartmentsForJurisdiction( + jurisdiction: string, + year: string | null = null, +): string[] { + const staticData = loadStaticData(); + const parts = jurisdiction.split("/"); + + // Determine the year to use + const targetYear = year || getLatestYearForJurisdiction(jurisdiction); + if (!targetYear) { + return []; + } + // Try to get departments from static data + if (parts.length === 1) { + // Check provincial first + const provinceData = staticData.structure.provincial[jurisdiction]; + if (provinceData?.departmentsByYear?.[targetYear]) { + return provinceData.departmentsByYear[targetYear]; + } + + // Check if it's a municipality (search across all provinces) + for (const province of Object.keys(staticData.structure.municipal)) { + const municipalityData = + staticData.structure.municipal[province]?.[jurisdiction]; + if (municipalityData?.departmentsByYear?.[targetYear]) { + return municipalityData.departmentsByYear[targetYear]; + } + } + } else if (parts.length === 2) { + // Municipal jurisdiction with explicit province + const [province, municipality] = parts; + const municipalityData = + staticData.structure.municipal[province]?.[municipality]; + if (municipalityData?.departmentsByYear?.[targetYear]) { + return municipalityData.departmentsByYear[targetYear]; + } + } + + // Fallback to filesystem if not in static data (shouldn't happen in production) + const jurisdictionPath = findJurisdictionDataPathWithYear( + jurisdiction, + targetYear, + ); if (!jurisdictionPath) { return []; } @@ -397,16 +563,31 @@ export function getDepartmentsForJurisdiction(jurisdiction: string): string[] { } } -export function getExpandedDepartments(jurisdiction: string): Department[] { - const slugs = getDepartmentsForJurisdiction(jurisdiction); - return slugs.map((slug) => getDepartmentData(jurisdiction, slug)); +/** + * Get expanded department data for all departments in a jurisdiction. + * Returns full department objects with all their data. + * + * @param jurisdiction - Slug in format "province" or "province/municipality" + * @param year - Optional year string (e.g., "2024") or null for latest + * @returns Array of full Department objects + */ +export function getExpandedDepartments( + jurisdiction: string, + year: string | null = null, +): Department[] { + const slugs = getDepartmentsForJurisdiction(jurisdiction, year); + return slugs.map((slug) => getDepartmentData(jurisdiction, slug, year)); } -export function departmentHref( - jurisdiction: string, - department: string, - locale?: string, -): string { - const path = `/${jurisdiction}/departments/${department}`; - return locale ? `/${locale}${path}` : path; +/** + * Get the last modified date for a file path from static data. + * The file path should be relative to the project root. + * + * @param relativePath - File path relative to project root (e.g., "data/provincial/ontario/2024/summary.json") + * @returns Date object if found in static data, undefined otherwise + */ +export function getFileLastModified(relativePath: string): Date | undefined { + const staticData = loadStaticData(); + const isoString = staticData.fileStats[relativePath]; + return isoString ? new Date(isoString) : undefined; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e0238b15..3c0748c1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -96,7 +96,6 @@ export function localizedPath(path: string, locale: string): string { const cleanPath = path.startsWith("/") ? path.slice(1) : path; // Check if path already starts with a locale - const locales = ["en", "fr"]; const pathParts = cleanPath.split("/"); const firstPart = pathParts[0]; diff --git a/src/locales/en.po b/src/locales/en.po index 3b1c25f6..2655ab1a 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -21,52 +21,64 @@ msgstr " How did Health Canada spend its budget in FY24?" msgid "— Canada Spends Team" msgstr "— Canada Spends Team" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:139 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:139 msgid "(inflation-adjusted to 2025 dollars)" msgstr "(inflation-adjusted to 2025 dollars)" #. placeholder {0}: department.agenciesHeading #. placeholder {0}: department.leadershipHeading #. placeholder {0}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:68 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:200 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:295 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:332 -#: src/components/DepartmentList.tsx:68 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:87 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:219 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:314 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:351 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:72 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:204 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:299 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:336 +#: src/components/DepartmentList.tsx:71 msgid "{0}" msgstr "{0}" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:389 +#: src/components/JurisdictionPageContent.tsx:374 msgid "{0} Government Departments explained" msgstr "{0} Government Departments explained" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:291 +#: src/components/JurisdictionPageContent.tsx:279 msgid "{0} Government Spending" msgstr "{0} Government Spending" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:359 +#: src/components/JurisdictionPageContent.tsx:344 msgid "{0} Government Workforce" msgstr "{0} Government Workforce" #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:303 +#: src/components/JurisdictionPageContent.tsx:291 msgid "{0}'s Revenue and Spending in Financial Year {1}" msgstr "{0}'s Revenue and Spending in Financial Year {1}" -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:105 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:142 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:158 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:188 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:236 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:281 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:320 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:384 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:420 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:462 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:124 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:161 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:177 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:207 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:255 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:300 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:339 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:403 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:109 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:146 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:162 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:192 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:240 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:285 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:324 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:388 +#: src/components/JurisdictionPageContent.tsx:406 +#: src/components/JurisdictionPageContent.tsx:448 msgid "{paragraph}" msgstr "{paragraph}" @@ -93,7 +105,7 @@ msgstr "10 government departments accounted for 73.2% of federal spending in FY msgid "10 government departments accounted for 73.2% of federal spending in FY 2024." msgstr "10 government departments accounted for 73.2% of federal spending in FY 2024." -#: src/app/[lang]/(main)/spending/page.tsx:185 +#: src/app/[lang]/(main)/federal/spending/page.tsx:194 msgid "80% of employees are in permanent roles" msgstr "80% of employees are in permanent roles" @@ -157,13 +169,13 @@ msgstr "A look at how Veterans Affairs Canada spends its budget" msgid "A significant portion of GAC's budget supports Canada's international development assistance, with key programs focused on climate adaptation, gender equality, and health initiatives in developing nations. The department also facilitates economic diplomacy and trade agreements that benefit Canadian businesses and secure investment opportunities abroad." msgstr "A significant portion of GAC's budget supports Canada's international development assistance, with key programs focused on climate adaptation, gender equality, and health initiatives in developing nations. The department also facilitates economic diplomacy and trade agreements that benefit Canadian businesses and secure investment opportunities abroad." -#: src/components/MainLayout/index.tsx:229 -#: src/components/MainLayout/index.tsx:377 +#: src/components/MainLayout/index.tsx:231 +#: src/components/MainLayout/index.tsx:379 msgid "About" msgstr "About" #: src/components/MainLayout/Footer.tsx:26 -#: src/components/MainLayout/index.tsx:243 +#: src/components/MainLayout/index.tsx:245 msgid "About Us" msgstr "About Us" @@ -200,7 +212,7 @@ msgstr "Acquisition of Lands, Buildings and Works" msgid "Acquisition of Machinery and Equipment" msgstr "Acquisition of Machinery and Equipment" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:126 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:126 msgid "Adjust for inflation (2025 dollars)" msgstr "Adjust for inflation (2025 dollars)" @@ -208,7 +220,7 @@ msgstr "Adjust for inflation (2025 dollars)" msgid "Adjust sliders to see how department spending reductions affect the overall Fall 2025 Budget. The Minister of Finance has asked departments to reduce spending by 7.5% in 2026-27, 10% in 2027-28, and 15% in 2028-29." msgstr "Adjust sliders to see how department spending reductions affect the overall Fall 2025 Budget. The Minister of Finance has asked departments to reduce spending by 7.5% in 2026-27, 10% in 2027-28, and 15% in 2028-29." -#: src/app/[lang]/(main)/spending/page.tsx:196 +#: src/app/[lang]/(main)/federal/spending/page.tsx:205 msgid "Age" msgstr "Age" @@ -244,12 +256,12 @@ msgstr "All Articles" msgid "All data presented regarding government spending is sourced directly from official government databases. Unless otherwise noted, we have used <0>Public Accounts of Canada data as the primary data source. While we strive to provide accurate, up-to-date information, we cannot guarantee the data's completeness, reliability, or timeliness. We assume no responsibility for any errors, omissions, or outcomes resulting from the use of this information. Please consult the original government sources for official and verified data." msgstr "All data presented regarding government spending is sourced directly from official government databases. Unless otherwise noted, we have used <0>Public Accounts of Canada data as the primary data source. While we strive to provide accurate, up-to-date information, we cannot guarantee the data's completeness, reliability, or timeliness. We assume no responsibility for any errors, omissions, or outcomes resulting from the use of this information. Please consult the original government sources for official and verified data." -#: src/app/[lang]/(main)/budget/page.tsx:297 +#: src/app/[lang]/(main)/federal/budget/page.tsx:307 msgid "All government budget data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. This page is also based on government memos and leaks, so it is not an official release of the Fall 2025 Budget. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." msgstr "All government budget data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. This page is also based on government memos and leaks, so it is not an official release of the Fall 2025 Budget. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:431 -#: src/app/[lang]/(main)/spending/page.tsx:269 +#: src/app/[lang]/(main)/federal/spending/page.tsx:278 +#: src/components/JurisdictionPageContent.tsx:417 msgid "All government spending data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." msgstr "All government spending data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." @@ -261,7 +273,7 @@ msgstr "All of the information we use comes from publicly available databases an msgid "All the information comes from public databases and reports by the Government of Canada. We show our sources, so you know exactly where it comes from." msgstr "All the information comes from public databases and reports by the Government of Canada. We show our sources, so you know exactly where it comes from." -#: src/app/[lang]/(main)/budget/page.tsx:63 +#: src/app/[lang]/(main)/federal/budget/page.tsx:62 msgid "Amount/Change" msgstr "Amount/Change" @@ -270,16 +282,16 @@ msgid "Annual Income (CAD)" msgstr "Annual Income (CAD)" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:240 +#: src/components/JurisdictionPageContent.tsx:228 msgid "Annual interest expense for {0}" msgstr "Annual interest expense for {0}" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:261 +#: src/components/JurisdictionPageContent.tsx:249 msgid "Annual municipal spending per {0} resident" msgstr "Annual municipal spending per {0} resident" -#: src/app/[lang]/(main)/spending/page.tsx:176 +#: src/app/[lang]/(main)/federal/spending/page.tsx:185 msgid "Annual payroll" msgstr "Annual payroll" @@ -296,12 +308,12 @@ msgid "Articles | Canada Spends" msgstr "Articles | Canada Spends" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:210 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:225 +#: src/components/JurisdictionPageContent.tsx:198 +#: src/components/JurisdictionPageContent.tsx:213 msgid "As of fiscal year end {0}" msgstr "As of fiscal year end {0}" -#: src/app/[lang]/(main)/spending/page.tsx:169 +#: src/app/[lang]/(main)/federal/spending/page.tsx:178 msgid "Average annual compensation" msgstr "Average annual compensation" @@ -310,7 +322,7 @@ msgid "Back to All Articles" msgstr "Back to All Articles" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:159 +#: src/components/JurisdictionPageContent.tsx:147 msgid "Balance for {0}" msgstr "Balance for {0}" @@ -338,16 +350,16 @@ msgstr "British Columbia HTP" msgid "British Columbia STP" msgstr "British Columbia STP" -#: src/components/MainLayout/index.tsx:134 -#: src/components/MainLayout/index.tsx:312 +#: src/components/MainLayout/index.tsx:136 +#: src/components/MainLayout/index.tsx:314 msgid "Budget" msgstr "Budget" -#: src/app/[lang]/(main)/budget/page.tsx:60 +#: src/app/[lang]/(main)/federal/budget/page.tsx:59 msgid "Budget Impact" msgstr "Budget Impact" -#: src/app/[lang]/(main)/budget/page.tsx:196 +#: src/app/[lang]/(main)/federal/budget/page.tsx:206 msgid "Budget Statistics (Projected FY 2025)" msgstr "Budget Statistics (Projected FY 2025)" @@ -417,7 +429,7 @@ msgstr "Canada-wide early learning and child care" msgid "Canada, you need the facts." msgstr "Canada, you need the facts." -#: src/app/[lang]/(main)/budget/page.tsx:231 +#: src/app/[lang]/(main)/federal/budget/page.tsx:241 msgid "Capital Investments" msgstr "Capital Investments" @@ -466,7 +478,7 @@ msgstr "Community Infrastructure Grants" msgid "Community Safety" msgstr "Community Safety" -#: src/app/[lang]/(main)/spending/page.tsx:167 +#: src/app/[lang]/(main)/federal/spending/page.tsx:176 msgid "Compensation per Employee" msgstr "Compensation per Employee" @@ -476,8 +488,8 @@ msgid "Connect with us" msgstr "Connect with us" #: src/components/MainLayout/Footer.tsx:43 -#: src/components/MainLayout/index.tsx:260 -#: src/components/MainLayout/index.tsx:384 +#: src/components/MainLayout/index.tsx:262 +#: src/components/MainLayout/index.tsx:386 msgid "Contact" msgstr "Contact" @@ -575,7 +587,7 @@ msgstr "Defence Procurement" msgid "Defence Team" msgstr "Defence Team" -#: src/app/[lang]/(main)/budget/page.tsx:242 +#: src/app/[lang]/(main)/federal/budget/page.tsx:252 msgid "Deficit" msgstr "Deficit" @@ -595,7 +607,8 @@ msgstr "Department of Finance, Spending by Entity, FY 2024 (in millions)" msgid "Department of National Defence" msgstr "Department of National Defence" -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:113 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:132 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:117 msgid "Department Spending" msgstr "Department Spending" @@ -603,7 +616,7 @@ msgstr "Department Spending" msgid "Department Spending Reductions" msgstr "Department Spending Reductions" -#: src/app/[lang]/(main)/spending/page.tsx:221 +#: src/app/[lang]/(main)/federal/spending/page.tsx:230 msgid "Departments + Agencies" msgstr "Departments + Agencies" @@ -742,15 +755,15 @@ msgstr "ESDC's share of federal spending in FY 2024" msgid "ESDC's share of federal spending in FY 2024 was lower than FY 1995" msgstr "ESDC's share of federal spending in FY 2024 was lower than FY 1995" -#: src/app/[lang]/(main)/budget/page.tsx:244 +#: src/app/[lang]/(main)/federal/budget/page.tsx:254 msgid "Est. projected budget deficit" msgstr "Est. projected budget deficit" -#: src/app/[lang]/(main)/budget/page.tsx:204 +#: src/app/[lang]/(main)/federal/budget/page.tsx:214 msgid "Est. projected government budget" msgstr "Est. projected government budget" -#: src/app/[lang]/(main)/budget/page.tsx:213 +#: src/app/[lang]/(main)/federal/budget/page.tsx:223 msgid "Est. projected government revenue" msgstr "Est. projected government revenue" @@ -770,7 +783,7 @@ msgstr "Estimated income tax paid to federal government" msgid "Estimated income tax paid to provincial government" msgstr "Estimated income tax paid to provincial government" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:365 +#: src/components/JurisdictionPageContent.tsx:350 msgid "Estimated public service workforce" msgstr "Estimated public service workforce" @@ -798,14 +811,22 @@ msgstr "Excise Tax — Gasoline" msgid "Explore Budget 2025" msgstr "Explore Budget 2025" +#: src/app/[lang]/(main)/federal/budget/layout.tsx:17 +msgid "Explore Canada's federal budget with interactive visualizations and detailed analysis of government revenue and expenditures." +msgstr "Explore Canada's federal budget with interactive visualizations and detailed analysis of government revenue and expenditures." + #: src/app/[lang]/(main)/page.tsx:72 msgid "Explore federal data" msgstr "Explore federal data" -#: src/app/[lang]/(main)/spending/page.tsx:241 +#: src/app/[lang]/(main)/federal/spending/page.tsx:250 msgid "Explore federal employee salary distribution by year and demographic group" msgstr "Explore federal employee salary distribution by year and demographic group" +#: src/app/[lang]/(main)/federal/spending/layout.tsx:17 +msgid "Explore how the Canadian federal government spends your tax dollars across departments and programs." +msgstr "Explore how the Canadian federal government spends your tax dollars across departments and programs." + #: src/app/[lang]/(main)/articles/page.tsx:19 msgid "Explore in-depth articles about Canadian government spending, budget analysis, and public finance. Stay informed about how your tax dollars are used through Canada Spends." msgstr "Explore in-depth articles about Canadian government spending, budget analysis, and public finance. Stay informed about how your tax dollars are used through Canada Spends." @@ -831,7 +852,7 @@ msgstr "Explore Ontario data" msgid "Explore other Federal Departments" msgstr "Explore other Federal Departments" -#: src/app/[lang]/(main)/spending/page.tsx:125 +#: src/app/[lang]/(main)/federal/spending/page.tsx:134 msgid "Explore revenue and spending categories or filter by agency for deeper insights." msgstr "Explore revenue and spending categories or filter by agency for deeper insights." @@ -856,11 +877,15 @@ msgstr "Facts about Spending" msgid "Featured Articles" msgstr "Featured Articles" -#: src/components/MainLayout/index.tsx:125 -#: src/components/MainLayout/index.tsx:302 +#: src/components/MainLayout/index.tsx:127 +#: src/components/MainLayout/index.tsx:304 msgid "Federal" msgstr "Federal" +#: src/app/[lang]/(main)/federal/budget/layout.tsx:16 +msgid "Federal Budget 2025 | Canada Spends" +msgstr "Federal Budget 2025 | Canada Spends" + #: src/app/[lang]/(main)/spending/department-of-finance/page.tsx:177 msgid "Federal departments often contain other entities including offices, crown corporations and agencies. In FY 2024, Department of Finance entities with the highest expenditures were the Office of the Superintendent of Financial Institutions, Office of the Auditor General, and the Financial Transactions and Reports Analysis Centre of Canada." msgstr "Federal departments often contain other entities including offices, crown corporations and agencies. In FY 2024, Department of Finance entities with the highest expenditures were the Office of the Superintendent of Financial Institutions, Office of the Auditor General, and the Financial Transactions and Reports Analysis Centre of Canada." @@ -901,10 +926,14 @@ msgstr "Federal departments often include additional agencies, commands, and ope msgid "Federal departments often include multiple agencies and service delivery arms. In FY 2024, ESDC's highest-expenditure entities included:" msgstr "Federal departments often include multiple agencies and service delivery arms. In FY 2024, ESDC's highest-expenditure entities included:" -#: src/app/[lang]/(main)/budget/page.tsx:173 +#: src/app/[lang]/(main)/federal/budget/page.tsx:183 msgid "Federal Fall 2025 Government Budget" msgstr "Federal Fall 2025 Government Budget" +#: src/app/[lang]/(main)/federal/spending/layout.tsx:16 +msgid "Federal Government Spending | Canada Spends" +msgstr "Federal Government Spending | Canada Spends" + #: src/app/[lang]/(main)/spending/department-of-finance/page.tsx:169 #: src/app/[lang]/(main)/spending/global-affairs-canada/page.tsx:196 #: src/app/[lang]/(main)/spending/indigenous-services-and-northern-affairs/page.tsx:172 @@ -919,7 +948,7 @@ msgstr "Federal government spending in FY 2024" msgid "Federal government spending isolated to FY 2024" msgstr "Federal government spending isolated to FY 2024" -#: src/app/[lang]/(main)/spending/page.tsx:223 +#: src/app/[lang]/(main)/federal/spending/page.tsx:232 msgid "Federal organizations" msgstr "Federal organizations" @@ -988,7 +1017,7 @@ msgid "Finance Canada" msgstr "Finance Canada" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:344 +#: src/components/JurisdictionPageContent.tsx:329 msgid "Financial Position {0}" msgstr "Financial Position {0}" @@ -1040,7 +1069,7 @@ msgstr "Functioning of Government" msgid "Future Force Design" msgstr "Future Force Design" -#: src/app/[lang]/(main)/spending/page.tsx:122 +#: src/app/[lang]/(main)/federal/spending/page.tsx:131 msgid "FY 2024 Government Revenue and Spending" msgstr "FY 2024 Government Revenue and Spending" @@ -1056,23 +1085,23 @@ msgstr "GAC's expenditures are divided across five primary categories:" msgid "Gender Equality" msgstr "Gender Equality" -#: src/app/[lang]/(main)/spending/page.tsx:114 +#: src/app/[lang]/(main)/federal/spending/page.tsx:123 msgid "Get data-driven insights into how governmental revenue and spending affect Canadian lives and programs." msgstr "Get data-driven insights into how governmental revenue and spending affect Canadian lives and programs." #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:294 +#: src/components/JurisdictionPageContent.tsx:282 msgid "Get data-driven insights into how the {0} government’s revenue and spending affect {1} residents and programs." msgstr "Get data-driven insights into how the {0} government’s revenue and spending affect {1} residents and programs." #: src/components/MainLayout/Footer.tsx:35 -#: src/components/MainLayout/index.tsx:252 +#: src/components/MainLayout/index.tsx:254 msgid "Get Involved" msgstr "Get Involved" #: src/app/[lang]/(main)/page.tsx:42 -#: src/app/[lang]/layout.tsx:23 +#: src/app/[lang]/layout.tsx:16 msgid "Get The Facts About Government Spending" msgstr "Get The Facts About Government Spending" @@ -1114,11 +1143,11 @@ msgstr "Goods and Services Tax" msgid "Gottfriedson Band Class Settlement" msgstr "Gottfriedson Band Class Settlement" -#: src/app/[lang]/(main)/spending/page.tsx:260 +#: src/app/[lang]/(main)/federal/spending/page.tsx:269 msgid "Government Departments explained" msgstr "Government Departments explained" -#: src/app/[lang]/(main)/budget/page.tsx:288 +#: src/app/[lang]/(main)/federal/budget/page.tsx:298 msgid "Government Departments Explained" msgstr "Government Departments Explained" @@ -1126,13 +1155,13 @@ msgstr "Government Departments Explained" msgid "Government IT Operations" msgstr "Government IT Operations" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:373 +#: src/components/JurisdictionPageContent.tsx:358 msgid "Government organizations" msgstr "Government organizations" -#: src/app/[lang]/(main)/spending/page.tsx:111 -#: src/components/MainLayout/index.tsx:111 -#: src/components/MainLayout/index.tsx:294 +#: src/app/[lang]/(main)/federal/spending/page.tsx:120 +#: src/components/MainLayout/index.tsx:113 +#: src/components/MainLayout/index.tsx:296 msgid "Government Spending" msgstr "Government Spending" @@ -1145,11 +1174,11 @@ msgstr "Government spending is based on 2023-2024 fiscal spending. Attempts have msgid "facts-1" msgstr "Government spending shouldn’t be a black box. Every year, the federal government spends hundreds of billions of dollars but most Canadians have no clue where it all goes. The data is available, but it’s buried on obscure websites and impossible to navigate." -#: src/app/[lang]/layout.tsx:24 +#: src/app/[lang]/layout.tsx:17 msgid "Government spending shouldn't be a black box. We turn complex data into clear, non-partisan insights so every Canadian knows where their money goes." msgstr "Government spending shouldn't be a black box. We turn complex data into clear, non-partisan insights so every Canadian knows where their money goes." -#: src/app/[lang]/(main)/spending/page.tsx:156 +#: src/app/[lang]/(main)/federal/spending/page.tsx:165 msgid "Government Workforce" msgstr "Government Workforce" @@ -1161,7 +1190,7 @@ msgstr "Government Workforce & Spending Data | See the Breakdown" msgid "Grants to Support the New Fiscal Relationship with First Nations" msgstr "Grants to Support the New Fiscal Relationship with First Nations" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:101 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:101 msgid "Group" msgstr "Group" @@ -1169,7 +1198,7 @@ msgstr "Group" msgid "Have questions or feedback? Email us at hi@canadaspends.com or connect with us on X @canada_spends - we'd love to hear from you!" msgstr "Have questions or feedback? Email us at hi@canadaspends.com or connect with us on X @canada_spends - we'd love to hear from you!" -#: src/app/[lang]/(main)/spending/page.tsx:160 +#: src/app/[lang]/(main)/federal/spending/page.tsx:169 msgid "Headcount" msgstr "Headcount" @@ -1341,8 +1370,10 @@ msgid "Immigration, Refugees and Citizenship Canada | Canada Spends" msgstr "Immigration, Refugees and Citizenship Canada | Canada Spends" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:119 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:126 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:138 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:145 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:123 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:130 msgid "In Financial Year {0}," msgstr "In Financial Year {0}," @@ -1415,7 +1446,7 @@ msgstr "In-depth analysis and insights about Canadian government spending, budge msgid "Income Tax Revenues" msgstr "Income Tax Revenues" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:8 +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:8 msgid "Indeterminate" msgstr "Indeterminate" @@ -1488,7 +1519,7 @@ msgstr "Innovation, Science and Industry Canada | Canada Spends" msgid "Innovative and Sustainable Natural Resources Development" msgstr "Innovative and Sustainable Natural Resources Development" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:232 +#: src/components/JurisdictionPageContent.tsx:220 msgid "Interest on Debt" msgstr "Interest on Debt" @@ -1574,13 +1605,13 @@ msgstr "Justice System" msgid "Labour Market Development Agreements (LMDAs): federal funding to provinces for job training and employment support." msgstr "Labour Market Development Agreements (LMDAs): federal funding to provinces for job training and employment support." -#: src/app/[lang]/(main)/budget/page.tsx:275 +#: src/app/[lang]/(main)/federal/budget/page.tsx:285 msgid "Latest Budget News & Impact" msgstr "Latest Budget News & Impact" #. placeholder {0}: jurisdiction.name #. placeholder {1}: ["Toronto", "Vancouver"].includes(jurisdiction.name) && ( Numbers are reported on an accrual basis. ) -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:309 +#: src/components/JurisdictionPageContent.tsx:297 msgid "Look back at what {0}'s government made and spent. {1}" msgstr "Look back at what {0}'s government made and spent. {1}" @@ -1637,11 +1668,11 @@ msgstr "Manitoba HTP" msgid "Manitoba STP" msgstr "Manitoba STP" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:403 +#: src/components/JurisdictionPageContent.tsx:389 msgid "Methodology" msgstr "Methodology" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:369 +#: src/components/JurisdictionPageContent.tsx:354 msgid "Ministries + Agencies" msgstr "Ministries + Agencies" @@ -1672,8 +1703,8 @@ msgstr "Most federal spending can be categorized as direct or indirect." msgid "Most federal spending can be categorized as direct or indirect. Direct spending refers to money the federal government spends on budget items such as federal programs, employee salaries, and debt interest. Indirect spending refers to federal transfers to other levels of government." msgstr "Most federal spending can be categorized as direct or indirect. Direct spending refers to money the federal government spends on budget items such as federal programs, employee salaries, and debt interest. Indirect spending refers to federal transfers to other levels of government." -#: src/components/MainLayout/index.tsx:164 -#: src/components/MainLayout/index.tsx:335 +#: src/components/MainLayout/index.tsx:166 +#: src/components/MainLayout/index.tsx:337 msgid "Municipal" msgstr "Municipal" @@ -1721,7 +1752,7 @@ msgstr "Net actuarial losses" msgid "Net actuarial losses (gains)" msgstr "Net actuarial losses (gains)" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:202 +#: src/components/JurisdictionPageContent.tsx:190 msgid "Net Debt" msgstr "Net Debt" @@ -1761,7 +1792,7 @@ msgstr "Newfoundland and Labrador HTP" msgid "Newfoundland and Labrador STP" msgstr "Newfoundland and Labrador STP" -#: src/app/[lang]/(main)/budget/page.tsx:57 +#: src/app/[lang]/(main)/federal/budget/page.tsx:56 msgid "News Source & Date" msgstr "News Source & Date" @@ -1801,7 +1832,7 @@ msgstr "Northwest Territories HTP" msgid "Northwest Territories STP" msgstr "Northwest Territories STP" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:159 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:159 msgid "Note the different salary ranges prior to 2023. Information for small numbers has been suppressed (values of 0 are actually counts of 1 to 5)." msgstr "Note the different salary ranges prior to 2023. Information for small numbers has been suppressed (values of 0 are actually counts of 1 to 5)." @@ -1821,11 +1852,11 @@ msgstr "Nova Scotia STP" msgid "Nuclear Labs + Decommissioning" msgstr "Nuclear Labs + Decommissioning" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:153 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:153 msgid "Number of Employees" msgstr "Number of Employees" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:312 +#: src/components/JurisdictionPageContent.tsx:300 msgid "Numbers are reported on an accrual basis." msgstr "Numbers are reported on an accrual basis." @@ -1847,7 +1878,13 @@ msgstr "Obligations" #. placeholder {0}: jurisdiction.name #. placeholder {1}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:130 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:149 +msgid "of {0} municipal spending was by {1}" +msgstr "of {0} municipal spending was by {1}" + +#. placeholder {0}: jurisdiction.name +#. placeholder {1}: department.name +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:134 msgid "of {0} provincial spending was by {1}" msgstr "of {0} provincial spending was by {1}" @@ -1911,7 +1948,7 @@ msgstr "Office of the Chief Electoral Officer" msgid "Office of the Secretary to the Governor General" msgstr "Office of the Secretary to the Governor General" -#: src/app/[lang]/(main)/budget/page.tsx:158 +#: src/app/[lang]/(main)/federal/budget/page.tsx:168 msgid "Official Fall 2025 Federal Budget Released" msgstr "Official Fall 2025 Federal Budget Released" @@ -1939,7 +1976,7 @@ msgstr "Ontario HTP" msgid "Ontario STP" msgstr "Ontario STP" -#: src/app/[lang]/(main)/budget/page.tsx:222 +#: src/app/[lang]/(main)/federal/budget/page.tsx:232 msgid "Operational Spend" msgstr "Operational Spend" @@ -1953,7 +1990,8 @@ msgid "Other" msgstr "Other" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:396 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:415 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:400 msgid "Other {0} Government Ministries" msgstr "Other {0} Government Ministries" @@ -2082,11 +2120,11 @@ msgstr "Parliament" msgid "Payroll Taxes" msgstr "Payroll Taxes" -#: src/app/[lang]/(main)/spending/page.tsx:228 +#: src/app/[lang]/(main)/federal/spending/page.tsx:237 msgid "PBO" msgstr "PBO" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:253 +#: src/components/JurisdictionPageContent.tsx:241 msgid "Per Capita Spending" msgstr "Per Capita Spending" @@ -2177,20 +2215,20 @@ msgstr "Professional + Special Services" msgid "Professional and Special Services" msgstr "Professional and Special Services" -#: src/app/[lang]/(main)/budget/page.tsx:233 +#: src/app/[lang]/(main)/federal/budget/page.tsx:243 msgid "Projected capital investments" msgstr "Projected capital investments" -#: src/app/[lang]/(main)/budget/page.tsx:224 +#: src/app/[lang]/(main)/federal/budget/page.tsx:234 msgid "Projected operational spending" msgstr "Projected operational spending" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:273 +#: src/components/JurisdictionPageContent.tsx:261 msgid "Property Tax Per Capita" msgstr "Property Tax Per Capita" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:281 +#: src/components/JurisdictionPageContent.tsx:269 msgid "Property tax revenue per {0} resident" msgstr "Property tax revenue per {0} resident" @@ -2198,8 +2236,8 @@ msgstr "Property tax revenue per {0} resident" msgid "Province/Territory" msgstr "Province/Territory" -#: src/components/MainLayout/index.tsx:140 -#: src/components/MainLayout/index.tsx:318 +#: src/components/MainLayout/index.tsx:142 +#: src/components/MainLayout/index.tsx:320 msgid "Provincial" msgstr "Provincial" @@ -2217,7 +2255,7 @@ msgstr "PSPC spent $8.3 billion in fiscal year (FY) 2024. This was 1.6% of the $ #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:379 +#: src/components/JurisdictionPageContent.tsx:364 msgid "Public Accounts of {0} FY {1}" msgstr "Public Accounts of {0} FY {1}" @@ -2263,7 +2301,7 @@ msgstr "Public Safety Canada spent $13.9 billion in fiscal year (FY) 2024, accou msgid "Public Safety, Democratic Institutions and Intergovernmental Affairs Canada (Public Safety Canada) is the federal department responsible for national security, emergency preparedness, and community safety. Established in 2003, it consolidates security, law enforcement, and emergency management functions. It includes the RCMP, CSIS, and CBSA and coordinates federal responses to threats and develops policies on crime prevention, cyber resilience, and disaster preparedness while overseeing intelligence-sharing with domestic and international partners." msgstr "Public Safety, Democratic Institutions and Intergovernmental Affairs Canada (Public Safety Canada) is the federal department responsible for national security, emergency preparedness, and community safety. Established in 2003, it consolidates security, law enforcement, and emergency management functions. It includes the RCMP, CSIS, and CBSA and coordinates federal responses to threats and develops policies on crime prevention, cyber resilience, and disaster preparedness while overseeing intelligence-sharing with domestic and international partners." -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:363 +#: src/components/JurisdictionPageContent.tsx:348 msgid "Public Service Employees" msgstr "Public Service Employees" @@ -2312,7 +2350,7 @@ msgstr "RCMP" msgid "Ready Forces" msgstr "Ready Forces" -#: src/app/[lang]/(main)/budget/page.tsx:278 +#: src/app/[lang]/(main)/federal/budget/page.tsx:288 msgid "Recent developments and their projected impact on the Fall 2025 Budget" msgstr "Recent developments and their projected impact on the Fall 2025 Budget" @@ -2364,7 +2402,7 @@ msgstr "Retirement Benefits" msgid "Return on Investments" msgstr "Return on Investments" -#: src/app/[lang]/(main)/budget/page.tsx:211 +#: src/app/[lang]/(main)/federal/budget/page.tsx:221 #: src/components/Sankey/BudgetSankey.tsx:456 #: src/components/Sankey/index.tsx:700 msgid "Revenue" @@ -2378,15 +2416,15 @@ msgstr "Revenue Canada" msgid "Safety" msgstr "Safety" -#: src/app/[lang]/(main)/spending/page.tsx:238 +#: src/app/[lang]/(main)/federal/spending/page.tsx:247 msgid "Salary Distribution" msgstr "Salary Distribution" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:134 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:134 msgid "Salary Distribution - {selectedYear} ({selectedGroup})" msgstr "Salary Distribution - {selectedYear} ({selectedGroup})" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:152 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:152 msgid "Salary Range" msgstr "Salary Range" @@ -2410,6 +2448,10 @@ msgstr "Saskatchewan STP" msgid "See how Canada's government spends tax dollars—track workforce data, spending trends, and federal debt with clear, non-partisan insights." msgstr "See how Canada's government spends tax dollars—track workforce data, spending trends, and federal debt with clear, non-partisan insights." +#: src/app/[lang]/(main)/tax-visualizer/layout.tsx:17 +msgid "See how your taxes are spent across federal and provincial programs. Interactive calculator for Canadian taxpayers." +msgstr "See how your taxes are spent across federal and provincial programs. Interactive calculator for Canadian taxpayers." + #: src/app/[lang]/(main)/spending/employment-and-social-development-canada/page.tsx:204 msgid "Service Canada: responsible for processing EI, CPP, and OAS benefits." msgstr "Service Canada: responsible for processing EI, CPP, and OAS benefits." @@ -2448,22 +2490,22 @@ msgstr "Social Security" msgid "Social Transfer to Provinces" msgstr "Social Transfer to Provinces" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:327 +#: src/components/JurisdictionPageContent.tsx:315 msgid "Source" msgstr "Source" -#: src/app/[lang]/(main)/spending/page.tsx:251 +#: src/app/[lang]/(main)/federal/spending/page.tsx:260 msgid "Source:" msgstr "Source:" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:428 -#: src/app/[lang]/(main)/budget/page.tsx:294 -#: src/app/[lang]/(main)/spending/page.tsx:266 +#: src/app/[lang]/(main)/federal/budget/page.tsx:304 +#: src/app/[lang]/(main)/federal/spending/page.tsx:275 +#: src/components/JurisdictionPageContent.tsx:414 msgid "Sources" msgstr "Sources" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:377 -#: src/app/[lang]/(main)/spending/page.tsx:226 +#: src/app/[lang]/(main)/federal/spending/page.tsx:235 +#: src/components/JurisdictionPageContent.tsx:362 msgid "Sources:" msgstr "Sources:" @@ -2476,8 +2518,8 @@ msgstr "Space" msgid "Spending" msgstr "Spending" -#: src/components/MainLayout/index.tsx:218 -#: src/components/MainLayout/index.tsx:370 +#: src/components/MainLayout/index.tsx:220 +#: src/components/MainLayout/index.tsx:372 msgid "Spending Database" msgstr "Spending Database" @@ -2501,7 +2543,7 @@ msgstr "Statistics Canada" msgid "Stay connected by following us on" msgstr "Stay connected by following us on" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:9 +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:9 msgid "Student + Casual" msgstr "Student + Casual" @@ -2525,7 +2567,7 @@ msgstr "Support for Global Competition" msgid "Support for Veterans" msgstr "Support for Veterans" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:152 +#: src/components/JurisdictionPageContent.tsx:140 msgid "Surplus/Deficit" msgstr "Surplus/Deficit" @@ -2533,15 +2575,19 @@ msgstr "Surplus/Deficit" msgid "Sustainable Bases, IT Systems, Infrastructure" msgstr "Sustainable Bases, IT Systems, Infrastructure" -#: src/components/MainLayout/index.tsx:363 +#: src/components/MainLayout/index.tsx:365 msgid "Tax Calculator" msgstr "Tax Calculator" -#: src/components/MainLayout/index.tsx:212 +#: src/components/MainLayout/index.tsx:214 msgid "Tax Visualizer" msgstr "Tax Visualizer" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:10 +#: src/app/[lang]/(main)/tax-visualizer/layout.tsx:16 +msgid "Tax Visualizer | Canada Spends" +msgstr "Tax Visualizer | Canada Spends" + +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:10 msgid "Term" msgstr "Term" @@ -2549,7 +2595,7 @@ msgstr "Term" msgid "Territorial Formula Financing" msgstr "Territorial Formula Financing" -#: src/app/[lang]/(main)/spending/page.tsx:199 +#: src/app/[lang]/(main)/federal/spending/page.tsx:208 msgid "The average employee is 43.3 years old" msgstr "The average employee is 43.3 years old" @@ -2697,7 +2743,7 @@ msgstr "The ministers work alongside the Deputy Minister of Foreign Affairs and msgid "The Public Services and Procurement Canada (PSPC) is the federal department responsible for providing centralized procurement, real estate management, pay and pension administration for federal employees, and translation services to the Government of Canada. It ensures that government departments have the goods, services, and infrastructure they need to operate efficiently while maintaining transparency, fairness, and value for taxpayers." msgstr "The Public Services and Procurement Canada (PSPC) is the federal department responsible for providing centralized procurement, real estate management, pay and pension administration for federal employees, and translation services to the Government of Canada. It ensures that government departments have the goods, services, and infrastructure they need to operate efficiently while maintaining transparency, fairness, and value for taxpayers." -#: src/app/[lang]/(main)/budget/page.tsx:184 +#: src/app/[lang]/(main)/federal/budget/page.tsx:194 msgid "The values you see here are based on the FY 2024 Budget with preliminary updates based on government announcements, memos, and leaks, and are meant to provide a rough idea of the budget. Once the official Fall 2025 Budget is released on November 4th, we will update this page to reflect the official budget." msgstr "The values you see here are based on the FY 2024 Budget with preliminary updates based on government announcements, memos, and leaks, and are meant to provide a rough idea of the budget. Once the official Fall 2025 Budget is released on November 4th, we will update this page to reflect the official budget." @@ -2712,7 +2758,7 @@ msgstr "The values you see here are based on the FY 2024 Budget with preliminary msgid "These Ministers are some of the <0>cabinet members who serve at the Prime Minister's discretion. Their tenure typically ends when they resign, are replaced, or when a new Prime Minister takes office and appoints a new cabinet. Outgoing ministers remain in their roles until their successors are sworn in." msgstr "These Ministers are some of the <0>cabinet members who serve at the Prime Minister's discretion. Their tenure typically ends when they resign, are replaced, or when a new Prime Minister takes office and appoints a new cabinet. Outgoing ministers remain in their roles until their successors are sworn in." -#: src/app/[lang]/(main)/budget/page.tsx:177 +#: src/app/[lang]/(main)/federal/budget/page.tsx:187 msgid "This page presents the official Fall 2025 Federal Budget as released by the Government of Canada on November 4th, 2025. All data is sourced directly from official government publications and public accounts from the Government of Canada." msgstr "This page presents the official Fall 2025 Federal Budget as released by the Government of Canada on November 4th, 2025. All data is sourced directly from official government publications and public accounts from the Government of Canada." @@ -2728,33 +2774,33 @@ msgstr "Through the Public Health Agency of Canada (PHAC), Health Canada monitor msgid "to help us support our mission." msgstr "to help us support our mission." -#: src/app/[lang]/(main)/budget/page.tsx:202 +#: src/app/[lang]/(main)/federal/budget/page.tsx:212 msgid "Total Budget" msgstr "Total Budget" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:217 +#: src/components/JurisdictionPageContent.tsx:205 msgid "Total Debt" msgstr "Total Debt" -#: src/app/[lang]/(main)/spending/page.tsx:162 +#: src/app/[lang]/(main)/federal/spending/page.tsx:171 msgid "Total full-time equivalents" msgstr "Total full-time equivalents" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:165 +#: src/components/JurisdictionPageContent.tsx:153 msgid "Total Revenue" msgstr "Total Revenue" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:172 +#: src/components/JurisdictionPageContent.tsx:160 msgid "Total revenue in {0}" msgstr "Total revenue in {0}" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:178 +#: src/components/JurisdictionPageContent.tsx:166 msgid "Total Spending" msgstr "Total Spending" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:186 +#: src/components/JurisdictionPageContent.tsx:174 msgid "Total spending in {0}" msgstr "Total spending in {0}" @@ -2762,7 +2808,7 @@ msgstr "Total spending in {0}" msgid "Total Tax" msgstr "Total Tax" -#: src/app/[lang]/(main)/spending/page.tsx:174 +#: src/app/[lang]/(main)/federal/spending/page.tsx:183 msgid "Total Wages" msgstr "Total Wages" @@ -2839,13 +2885,13 @@ msgstr "Transportation + Communication" msgid "Transportation and Communication" msgstr "Transportation and Communication" -#: src/app/[lang]/(main)/spending/page.tsx:232 -#: src/app/[lang]/(main)/spending/page.tsx:253 +#: src/app/[lang]/(main)/federal/spending/page.tsx:241 +#: src/app/[lang]/(main)/federal/spending/page.tsx:262 #: src/components/Sankey/index.tsx:346 msgid "Treasury Board" msgstr "Treasury Board" -#: src/app/[lang]/(main)/spending/page.tsx:182 +#: src/app/[lang]/(main)/federal/spending/page.tsx:191 msgid "Type of Tenure" msgstr "Type of Tenure" @@ -2894,13 +2940,13 @@ msgstr "Veterans Affairs Canada (VAC) is led by the Minister of Veterans Affairs msgid "Veterans Affairs Canada | Canada Spends" msgstr "Veterans Affairs Canada | Canada Spends" -#: src/app/[lang]/(main)/budget/page.tsx:164 +#: src/app/[lang]/(main)/federal/budget/page.tsx:174 msgid "View Federal 2025 Budget Details" msgstr "View Federal 2025 Budget Details" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:338 -#: src/app/[lang]/(main)/budget/page.tsx:269 -#: src/app/[lang]/(main)/spending/page.tsx:149 +#: src/app/[lang]/(main)/federal/budget/page.tsx:279 +#: src/app/[lang]/(main)/federal/spending/page.tsx:158 +#: src/components/JurisdictionPageContent.tsx:323 msgid "View this chart in full screen" msgstr "View this chart in full screen" @@ -2909,7 +2955,8 @@ msgid "Visitors, International Students + Temporary Workers" msgstr "Visitors, International Students + Temporary Workers" #. placeholder {0}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:122 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:141 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:126 msgid "was spent by {0}" msgstr "was spent by {0}" @@ -3102,7 +3149,7 @@ msgstr "Who Leads Veterans Affairs Canada (VAC)?" msgid "Why did you start this project?" msgstr "Why did you start this project?" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:80 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:80 msgid "Year" msgstr "Year" diff --git a/src/locales/fr.po b/src/locales/fr.po index 448daf4e..bcf1b2d1 100644 --- a/src/locales/fr.po +++ b/src/locales/fr.po @@ -21,52 +21,64 @@ msgstr "Comment Santé Canada a-t-il dépensé son budget au cours de l'exercice msgid "— Canada Spends Team" msgstr "— L'équipe de Canada Spends" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:139 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:139 msgid "(inflation-adjusted to 2025 dollars)" msgstr "(ajusté pour l'inflation en dollars de 2025)" #. placeholder {0}: department.agenciesHeading #. placeholder {0}: department.leadershipHeading #. placeholder {0}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:68 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:200 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:295 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:332 -#: src/components/DepartmentList.tsx:68 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:87 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:219 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:314 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:351 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:72 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:204 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:299 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:336 +#: src/components/DepartmentList.tsx:71 msgid "{0}" msgstr "" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:389 +#: src/components/JurisdictionPageContent.tsx:374 msgid "{0} Government Departments explained" msgstr "" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:291 +#: src/components/JurisdictionPageContent.tsx:279 msgid "{0} Government Spending" msgstr "" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:359 +#: src/components/JurisdictionPageContent.tsx:344 msgid "{0} Government Workforce" msgstr "" #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:303 +#: src/components/JurisdictionPageContent.tsx:291 msgid "{0}'s Revenue and Spending in Financial Year {1}" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:105 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:142 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:158 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:188 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:236 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:281 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:320 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:384 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:420 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:462 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:124 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:161 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:177 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:207 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:255 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:300 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:339 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:403 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:109 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:146 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:162 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:192 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:240 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:285 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:324 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:388 +#: src/components/JurisdictionPageContent.tsx:406 +#: src/components/JurisdictionPageContent.tsx:448 msgid "{paragraph}" msgstr "" @@ -93,7 +105,7 @@ msgstr "10 ministères gouvernementaux représentaient 73,2 % des dépenses féd msgid "10 government departments accounted for 73.2% of federal spending in FY 2024." msgstr "10 ministères gouvernementaux représentaient 73,2 % des dépenses fédérales pour l'exercice 2024." -#: src/app/[lang]/(main)/spending/page.tsx:185 +#: src/app/[lang]/(main)/federal/spending/page.tsx:194 msgid "80% of employees are in permanent roles" msgstr "80 % des employés occupent des postes permanents" @@ -157,13 +169,13 @@ msgstr "Un aperçu de la façon dont Anciens Combattants Canada dépense son bud msgid "A significant portion of GAC's budget supports Canada's international development assistance, with key programs focused on climate adaptation, gender equality, and health initiatives in developing nations. The department also facilitates economic diplomacy and trade agreements that benefit Canadian businesses and secure investment opportunities abroad." msgstr "Une partie importante du budget d'AMC soutient l'aide au développement international du Canada, avec des programmes clés axés sur l'adaptation au climat, l'égalité des genres et les initiatives de santé dans les pays en développement. Le ministère facilite également la diplomatie économique et les accords commerciaux qui profitent aux entreprises canadiennes et sécurisent les opportunités d'investissement à l'étranger." -#: src/components/MainLayout/index.tsx:229 -#: src/components/MainLayout/index.tsx:377 +#: src/components/MainLayout/index.tsx:231 +#: src/components/MainLayout/index.tsx:379 msgid "About" msgstr "À propos" #: src/components/MainLayout/Footer.tsx:26 -#: src/components/MainLayout/index.tsx:243 +#: src/components/MainLayout/index.tsx:245 msgid "About Us" msgstr "" @@ -200,7 +212,7 @@ msgstr "Acquisition de terrains, de bâtiments et de travaux" msgid "Acquisition of Machinery and Equipment" msgstr "Acquisition de machines et d'équipement" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:126 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:126 msgid "Adjust for inflation (2025 dollars)" msgstr "Ajuster pour l'inflation (dollars de 2025)" @@ -208,7 +220,7 @@ msgstr "Ajuster pour l'inflation (dollars de 2025)" msgid "Adjust sliders to see how department spending reductions affect the overall Fall 2025 Budget. The Minister of Finance has asked departments to reduce spending by 7.5% in 2026-27, 10% in 2027-28, and 15% in 2028-29." msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:196 +#: src/app/[lang]/(main)/federal/spending/page.tsx:205 msgid "Age" msgstr "Âge" @@ -244,12 +256,12 @@ msgstr "" msgid "All data presented regarding government spending is sourced directly from official government databases. Unless otherwise noted, we have used <0>Public Accounts of Canada data as the primary data source. While we strive to provide accurate, up-to-date information, we cannot guarantee the data's completeness, reliability, or timeliness. We assume no responsibility for any errors, omissions, or outcomes resulting from the use of this information. Please consult the original government sources for official and verified data." msgstr "Toutes les données présentées concernant les dépenses gouvernementales proviennent directement des bases de données officielles du gouvernement. Sauf indication contraire, nous avons utilisé les données des <0>Comptes publics du Canada comme source principale. Bien que nous nous efforcions de fournir des informations précises et à jour, nous ne pouvons garantir l'exhaustivité, la fiabilité ou l'actualité des données. Nous n'assumons aucune responsabilité pour les erreurs, omissions ou résultats découlant de l'utilisation de ces informations. Veuillez consulter les sources gouvernementales originales pour des données officielles et vérifiées." -#: src/app/[lang]/(main)/budget/page.tsx:297 +#: src/app/[lang]/(main)/federal/budget/page.tsx:307 msgid "All government budget data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. This page is also based on government memos and leaks, so it is not an official release of the Fall 2025 Budget. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:431 -#: src/app/[lang]/(main)/spending/page.tsx:269 +#: src/app/[lang]/(main)/federal/spending/page.tsx:278 +#: src/components/JurisdictionPageContent.tsx:417 msgid "All government spending data is sourced from official databases, but due to the complexity of these systems, occasional errors may occur despite our best efforts. We aim to make this information more accessible and accurate, and we welcome feedback. If you notice any issues, please let us know <0>here — we appreciate it and will work to address them promptly." msgstr "Toutes les données sur les dépenses gouvernementales proviennent de bases de données officielles, mais en raison de la complexité de ces systèmes, des erreurs occasionnelles peuvent survenir malgré nos meilleurs efforts. Nous visons à rendre cette information plus accessible et précise, et nous accueillons vos commentaires. Si vous remarquez des problèmes, veuillez nous en informer <0>ici — nous l'apprécions et nous travaillerons à les résoudre rapidement." @@ -261,7 +273,7 @@ msgstr "Toutes les informations que nous utilisons proviennent des bases de donn msgid "All the information comes from public databases and reports by the Government of Canada. We show our sources, so you know exactly where it comes from." msgstr "Toutes les informations proviennent des bases de données publiques et des rapports du gouvernement du Canada. Nous indiquons nos sources pour que vous sachiez exactement d'où elles proviennent." -#: src/app/[lang]/(main)/budget/page.tsx:63 +#: src/app/[lang]/(main)/federal/budget/page.tsx:62 msgid "Amount/Change" msgstr "" @@ -270,16 +282,16 @@ msgid "Annual Income (CAD)" msgstr "" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:240 +#: src/components/JurisdictionPageContent.tsx:228 msgid "Annual interest expense for {0}" msgstr "" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:261 +#: src/components/JurisdictionPageContent.tsx:249 msgid "Annual municipal spending per {0} resident" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:176 +#: src/app/[lang]/(main)/federal/spending/page.tsx:185 msgid "Annual payroll" msgstr "Masse salariale annuelle" @@ -296,12 +308,12 @@ msgid "Articles | Canada Spends" msgstr "" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:210 -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:225 +#: src/components/JurisdictionPageContent.tsx:198 +#: src/components/JurisdictionPageContent.tsx:213 msgid "As of fiscal year end {0}" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:169 +#: src/app/[lang]/(main)/federal/spending/page.tsx:178 msgid "Average annual compensation" msgstr "" @@ -310,7 +322,7 @@ msgid "Back to All Articles" msgstr "" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:159 +#: src/components/JurisdictionPageContent.tsx:147 msgid "Balance for {0}" msgstr "" @@ -338,16 +350,16 @@ msgstr "TCS Colombie-Britannique" msgid "British Columbia STP" msgstr "TCS Colombie-Britannique" -#: src/components/MainLayout/index.tsx:134 -#: src/components/MainLayout/index.tsx:312 +#: src/components/MainLayout/index.tsx:136 +#: src/components/MainLayout/index.tsx:314 msgid "Budget" msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:60 +#: src/app/[lang]/(main)/federal/budget/page.tsx:59 msgid "Budget Impact" msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:196 +#: src/app/[lang]/(main)/federal/budget/page.tsx:206 msgid "Budget Statistics (Projected FY 2025)" msgstr "" @@ -417,7 +429,7 @@ msgstr "" msgid "Canada, you need the facts." msgstr "Canada, vous avez besoin des faits." -#: src/app/[lang]/(main)/budget/page.tsx:231 +#: src/app/[lang]/(main)/federal/budget/page.tsx:241 msgid "Capital Investments" msgstr "" @@ -466,7 +478,7 @@ msgstr "Subventions pour l'infrastructure communautaire" msgid "Community Safety" msgstr "Sécurité communautaire" -#: src/app/[lang]/(main)/spending/page.tsx:167 +#: src/app/[lang]/(main)/federal/spending/page.tsx:176 msgid "Compensation per Employee" msgstr "Rémunération par employé" @@ -476,8 +488,8 @@ msgid "Connect with us" msgstr "Connectez-vous avec nous" #: src/components/MainLayout/Footer.tsx:43 -#: src/components/MainLayout/index.tsx:260 -#: src/components/MainLayout/index.tsx:384 +#: src/components/MainLayout/index.tsx:262 +#: src/components/MainLayout/index.tsx:386 msgid "Contact" msgstr "Contact" @@ -575,7 +587,7 @@ msgstr "Approvisionnement de la défense" msgid "Defence Team" msgstr "Équipe de la défense" -#: src/app/[lang]/(main)/budget/page.tsx:242 +#: src/app/[lang]/(main)/federal/budget/page.tsx:252 msgid "Deficit" msgstr "" @@ -595,7 +607,8 @@ msgstr "Ministère des Finances, Dépenses par entité, exercice 2024 (en millio msgid "Department of National Defence" msgstr "Ministère de la Défense nationale" -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:113 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:132 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:117 msgid "Department Spending" msgstr "" @@ -603,7 +616,7 @@ msgstr "" msgid "Department Spending Reductions" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:221 +#: src/app/[lang]/(main)/federal/spending/page.tsx:230 msgid "Departments + Agencies" msgstr "Ministères + Organismes" @@ -742,15 +755,15 @@ msgstr "Part des dépenses fédérales d'EDSC pour l'exercice 2024" msgid "ESDC's share of federal spending in FY 2024 was lower than FY 1995" msgstr "La part des dépenses fédérales d'EDSC en 2024 était inférieure à celle de 1995" -#: src/app/[lang]/(main)/budget/page.tsx:244 +#: src/app/[lang]/(main)/federal/budget/page.tsx:254 msgid "Est. projected budget deficit" msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:204 +#: src/app/[lang]/(main)/federal/budget/page.tsx:214 msgid "Est. projected government budget" msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:213 +#: src/app/[lang]/(main)/federal/budget/page.tsx:223 msgid "Est. projected government revenue" msgstr "" @@ -770,7 +783,7 @@ msgstr "" msgid "Estimated income tax paid to provincial government" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:365 +#: src/components/JurisdictionPageContent.tsx:350 msgid "Estimated public service workforce" msgstr "" @@ -798,14 +811,22 @@ msgstr "Taxe d'accise — Essence" msgid "Explore Budget 2025" msgstr "" +#: src/app/[lang]/(main)/federal/budget/layout.tsx:17 +msgid "Explore Canada's federal budget with interactive visualizations and detailed analysis of government revenue and expenditures." +msgstr "" + #: src/app/[lang]/(main)/page.tsx:72 msgid "Explore federal data" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:241 +#: src/app/[lang]/(main)/federal/spending/page.tsx:250 msgid "Explore federal employee salary distribution by year and demographic group" msgstr "" +#: src/app/[lang]/(main)/federal/spending/layout.tsx:17 +msgid "Explore how the Canadian federal government spends your tax dollars across departments and programs." +msgstr "" + #: src/app/[lang]/(main)/articles/page.tsx:19 msgid "Explore in-depth articles about Canadian government spending, budget analysis, and public finance. Stay informed about how your tax dollars are used through Canada Spends." msgstr "" @@ -831,7 +852,7 @@ msgstr "" msgid "Explore other Federal Departments" msgstr "Explorer d'autres ministères fédéraux" -#: src/app/[lang]/(main)/spending/page.tsx:125 +#: src/app/[lang]/(main)/federal/spending/page.tsx:134 msgid "Explore revenue and spending categories or filter by agency for deeper insights." msgstr "Explorez les catégories de revenus et de dépenses ou filtrez par organisme pour des analyses plus approfondies." @@ -856,11 +877,15 @@ msgstr "Faits sur les dépenses" msgid "Featured Articles" msgstr "" -#: src/components/MainLayout/index.tsx:125 -#: src/components/MainLayout/index.tsx:302 +#: src/components/MainLayout/index.tsx:127 +#: src/components/MainLayout/index.tsx:304 msgid "Federal" msgstr "" +#: src/app/[lang]/(main)/federal/budget/layout.tsx:16 +msgid "Federal Budget 2025 | Canada Spends" +msgstr "" + #: src/app/[lang]/(main)/spending/department-of-finance/page.tsx:177 msgid "Federal departments often contain other entities including offices, crown corporations and agencies. In FY 2024, Department of Finance entities with the highest expenditures were the Office of the Superintendent of Financial Institutions, Office of the Auditor General, and the Financial Transactions and Reports Analysis Centre of Canada." msgstr "Les ministères fédéraux comprennent souvent d'autres entités, notamment des bureaux, des sociétés d'État et des organismes. Au cours de l'exercice 2024, les entités du ministère des Finances ayant les dépenses les plus élevées étaient le Bureau du surintendant des institutions financières, le Bureau du vérificateur général et le Centre d'analyse des opérations et déclarations financières du Canada." @@ -901,10 +926,14 @@ msgstr "Les ministères fédéraux comprennent souvent des agences supplémentai msgid "Federal departments often include multiple agencies and service delivery arms. In FY 2024, ESDC's highest-expenditure entities included:" msgstr "Les ministères fédéraux comprennent souvent plusieurs agences et organes de prestation de services. Au cours de l'exercice 2024, les entités d'EDSC ayant les dépenses les plus élevées comprenaient :" -#: src/app/[lang]/(main)/budget/page.tsx:173 +#: src/app/[lang]/(main)/federal/budget/page.tsx:183 msgid "Federal Fall 2025 Government Budget" msgstr "" +#: src/app/[lang]/(main)/federal/spending/layout.tsx:16 +msgid "Federal Government Spending | Canada Spends" +msgstr "" + #: src/app/[lang]/(main)/spending/department-of-finance/page.tsx:169 #: src/app/[lang]/(main)/spending/global-affairs-canada/page.tsx:196 #: src/app/[lang]/(main)/spending/indigenous-services-and-northern-affairs/page.tsx:172 @@ -919,7 +948,7 @@ msgstr "Dépenses du gouvernement fédéral pour l'exercice 2024" msgid "Federal government spending isolated to FY 2024" msgstr "Dépenses du gouvernement fédéral isolées pour l'exercice 2024" -#: src/app/[lang]/(main)/spending/page.tsx:223 +#: src/app/[lang]/(main)/federal/spending/page.tsx:232 msgid "Federal organizations" msgstr "Organisations fédérales" @@ -988,7 +1017,7 @@ msgid "Finance Canada" msgstr "Finance Canada" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:344 +#: src/components/JurisdictionPageContent.tsx:329 msgid "Financial Position {0}" msgstr "" @@ -1040,7 +1069,7 @@ msgstr "Fonctionnement du gouvernement" msgid "Future Force Design" msgstr "Conception des forces futures" -#: src/app/[lang]/(main)/spending/page.tsx:122 +#: src/app/[lang]/(main)/federal/spending/page.tsx:131 msgid "FY 2024 Government Revenue and Spending" msgstr "Revenus et dépenses du gouvernement pour l'exercice 2024" @@ -1056,23 +1085,23 @@ msgstr "Les dépenses d'AMC sont réparties en cinq catégories principales :" msgid "Gender Equality" msgstr "Égalité des genres" -#: src/app/[lang]/(main)/spending/page.tsx:114 +#: src/app/[lang]/(main)/federal/spending/page.tsx:123 msgid "Get data-driven insights into how governmental revenue and spending affect Canadian lives and programs." msgstr "Obtenez des analyses basées sur les données sur la façon dont les revenus et les dépenses gouvernementaux affectent la vie des Canadiens et les programmes." #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:294 +#: src/components/JurisdictionPageContent.tsx:282 msgid "Get data-driven insights into how the {0} government’s revenue and spending affect {1} residents and programs." msgstr "" #: src/components/MainLayout/Footer.tsx:35 -#: src/components/MainLayout/index.tsx:252 +#: src/components/MainLayout/index.tsx:254 msgid "Get Involved" msgstr "" #: src/app/[lang]/(main)/page.tsx:42 -#: src/app/[lang]/layout.tsx:23 +#: src/app/[lang]/layout.tsx:16 msgid "Get The Facts About Government Spending" msgstr "Découvrez les faits sur les dépenses gouvernementales" @@ -1114,11 +1143,11 @@ msgstr "Taxe sur les produits et services" msgid "Gottfriedson Band Class Settlement" msgstr "Règlement du recours collectif de la bande Gottfriedson" -#: src/app/[lang]/(main)/spending/page.tsx:260 +#: src/app/[lang]/(main)/federal/spending/page.tsx:269 msgid "Government Departments explained" msgstr "Explication des ministères gouvernementaux" -#: src/app/[lang]/(main)/budget/page.tsx:288 +#: src/app/[lang]/(main)/federal/budget/page.tsx:298 msgid "Government Departments Explained" msgstr "" @@ -1126,13 +1155,13 @@ msgstr "" msgid "Government IT Operations" msgstr "Opérations informatiques gouvernementales" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:373 +#: src/components/JurisdictionPageContent.tsx:358 msgid "Government organizations" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:111 -#: src/components/MainLayout/index.tsx:111 -#: src/components/MainLayout/index.tsx:294 +#: src/app/[lang]/(main)/federal/spending/page.tsx:120 +#: src/components/MainLayout/index.tsx:113 +#: src/components/MainLayout/index.tsx:296 msgid "Government Spending" msgstr "Dépenses gouvernementales" @@ -1145,11 +1174,11 @@ msgstr "" msgid "facts-1" msgstr "Les dépenses gouvernementales ne devraient pas être une boîte noire. Chaque année, le gouvernement fédéral dépense des centaines de milliards de dollars, mais la plupart des Canadiens n'ont aucune idée où tout cet argent va. Les données sont disponibles, mais elles sont enfouies sur des sites Web obscurs et impossibles à naviguer." -#: src/app/[lang]/layout.tsx:24 +#: src/app/[lang]/layout.tsx:17 msgid "Government spending shouldn't be a black box. We turn complex data into clear, non-partisan insights so every Canadian knows where their money goes." -msgstr "Les dépenses gouvernementales ne devraient pas être une boîte noire. Nous transformons des données complexes en analyses claires et non partisanes pour que chaque Canadien sache où va son argent." +msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:156 +#: src/app/[lang]/(main)/federal/spending/page.tsx:165 msgid "Government Workforce" msgstr "Effectif gouvernemental" @@ -1161,7 +1190,7 @@ msgstr "Données sur l'effectif et les dépenses gouvernementales | Voir la rép msgid "Grants to Support the New Fiscal Relationship with First Nations" msgstr "Subventions pour soutenir la nouvelle relation financière avec les Premières Nations" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:101 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:101 msgid "Group" msgstr "Groupe" @@ -1169,7 +1198,7 @@ msgstr "Groupe" msgid "Have questions or feedback? Email us at hi@canadaspends.com or connect with us on X @canada_spends - we'd love to hear from you!" msgstr "Vous avez des questions ou des commentaires? Envoyez-nous un courriel à hi@canadaspends.com ou connectez-vous avec nous sur X @canada_spends - nous aimerions avoir de vos nouvelles!" -#: src/app/[lang]/(main)/spending/page.tsx:160 +#: src/app/[lang]/(main)/federal/spending/page.tsx:169 msgid "Headcount" msgstr "Effectif" @@ -1341,8 +1370,10 @@ msgid "Immigration, Refugees and Citizenship Canada | Canada Spends" msgstr "Immigration, Réfugiés et Citoyenneté Canada | Canada Spends" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:119 -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:126 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:138 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:145 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:123 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:130 msgid "In Financial Year {0}," msgstr "" @@ -1415,7 +1446,7 @@ msgstr "" msgid "Income Tax Revenues" msgstr "" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:8 +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:8 msgid "Indeterminate" msgstr "Indéterminé" @@ -1488,7 +1519,7 @@ msgstr "Innovation, Sciences et Industrie Canada | Canada Spends" msgid "Innovative and Sustainable Natural Resources Development" msgstr "Développement innovant et durable des ressources naturelles" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:232 +#: src/components/JurisdictionPageContent.tsx:220 msgid "Interest on Debt" msgstr "" @@ -1574,13 +1605,13 @@ msgstr "Système de justice" msgid "Labour Market Development Agreements (LMDAs): federal funding to provinces for job training and employment support." msgstr "Ententes sur le développement du marché du travail (EDMT) : financement fédéral aux provinces pour la formation professionnelle et le soutien à l'emploi." -#: src/app/[lang]/(main)/budget/page.tsx:275 +#: src/app/[lang]/(main)/federal/budget/page.tsx:285 msgid "Latest Budget News & Impact" msgstr "" #. placeholder {0}: jurisdiction.name #. placeholder {1}: ["Toronto", "Vancouver"].includes(jurisdiction.name) && ( Numbers are reported on an accrual basis. ) -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:309 +#: src/components/JurisdictionPageContent.tsx:297 msgid "Look back at what {0}'s government made and spent. {1}" msgstr "" @@ -1637,11 +1668,11 @@ msgstr "TCS Manitoba" msgid "Manitoba STP" msgstr "TPS Manitoba" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:403 +#: src/components/JurisdictionPageContent.tsx:389 msgid "Methodology" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:369 +#: src/components/JurisdictionPageContent.tsx:354 msgid "Ministries + Agencies" msgstr "" @@ -1672,8 +1703,8 @@ msgstr "La plupart des dépenses fédérales peuvent être classées comme direc msgid "Most federal spending can be categorized as direct or indirect. Direct spending refers to money the federal government spends on budget items such as federal programs, employee salaries, and debt interest. Indirect spending refers to federal transfers to other levels of government." msgstr "La plupart des dépenses fédérales peuvent être classées comme directes ou indirectes. Les dépenses directes font référence à l'argent que le gouvernement fédéral dépense pour des postes budgétaires tels que les programmes fédéraux, les salaires des employés et les intérêts sur la dette. Les dépenses indirectes font référence aux transferts fédéraux vers d'autres niveaux de gouvernement." -#: src/components/MainLayout/index.tsx:164 -#: src/components/MainLayout/index.tsx:335 +#: src/components/MainLayout/index.tsx:166 +#: src/components/MainLayout/index.tsx:337 msgid "Municipal" msgstr "" @@ -1721,7 +1752,7 @@ msgstr "Pertes actuarielles nettes" msgid "Net actuarial losses (gains)" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:202 +#: src/components/JurisdictionPageContent.tsx:190 msgid "Net Debt" msgstr "" @@ -1761,7 +1792,7 @@ msgstr "TCS Terre-Neuve-et-Labrador" msgid "Newfoundland and Labrador STP" msgstr "TPS Terre-Neuve-et-Labrador" -#: src/app/[lang]/(main)/budget/page.tsx:57 +#: src/app/[lang]/(main)/federal/budget/page.tsx:56 msgid "News Source & Date" msgstr "" @@ -1801,7 +1832,7 @@ msgstr "TCS Territoires du Nord-Ouest" msgid "Northwest Territories STP" msgstr "TPS Territoires du Nord-Ouest" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:159 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:159 msgid "Note the different salary ranges prior to 2023. Information for small numbers has been suppressed (values of 0 are actually counts of 1 to 5)." msgstr "" @@ -1821,11 +1852,11 @@ msgstr "TPS Nouvelle-Écosse" msgid "Nuclear Labs + Decommissioning" msgstr "Laboratoires nucléaires + Déclassement" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:153 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:153 msgid "Number of Employees" msgstr "Nombre d'employés" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:312 +#: src/components/JurisdictionPageContent.tsx:300 msgid "Numbers are reported on an accrual basis." msgstr "" @@ -1847,7 +1878,13 @@ msgstr "Obligations" #. placeholder {0}: jurisdiction.name #. placeholder {1}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:130 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:149 +msgid "of {0} municipal spending was by {1}" +msgstr "" + +#. placeholder {0}: jurisdiction.name +#. placeholder {1}: department.name +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:134 msgid "of {0} provincial spending was by {1}" msgstr "" @@ -1911,7 +1948,7 @@ msgstr "Bureau du directeur général des élections" msgid "Office of the Secretary to the Governor General" msgstr "Bureau du secrétaire du gouverneur général" -#: src/app/[lang]/(main)/budget/page.tsx:158 +#: src/app/[lang]/(main)/federal/budget/page.tsx:168 msgid "Official Fall 2025 Federal Budget Released" msgstr "" @@ -1939,7 +1976,7 @@ msgstr "TCS Ontario" msgid "Ontario STP" msgstr "TPS Ontario" -#: src/app/[lang]/(main)/budget/page.tsx:222 +#: src/app/[lang]/(main)/federal/budget/page.tsx:232 msgid "Operational Spend" msgstr "" @@ -1953,7 +1990,8 @@ msgid "Other" msgstr "Autre" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:396 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:415 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:400 msgid "Other {0} Government Ministries" msgstr "" @@ -2082,11 +2120,11 @@ msgstr "Parlement" msgid "Payroll Taxes" msgstr "Charges sociales" -#: src/app/[lang]/(main)/spending/page.tsx:228 +#: src/app/[lang]/(main)/federal/spending/page.tsx:237 msgid "PBO" msgstr "DPB" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:253 +#: src/components/JurisdictionPageContent.tsx:241 msgid "Per Capita Spending" msgstr "" @@ -2177,20 +2215,20 @@ msgstr "Services professionnels + spéciaux" msgid "Professional and Special Services" msgstr "Services professionnels et spéciaux" -#: src/app/[lang]/(main)/budget/page.tsx:233 +#: src/app/[lang]/(main)/federal/budget/page.tsx:243 msgid "Projected capital investments" msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:224 +#: src/app/[lang]/(main)/federal/budget/page.tsx:234 msgid "Projected operational spending" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:273 +#: src/components/JurisdictionPageContent.tsx:261 msgid "Property Tax Per Capita" msgstr "" #. placeholder {0}: jurisdiction.name -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:281 +#: src/components/JurisdictionPageContent.tsx:269 msgid "Property tax revenue per {0} resident" msgstr "" @@ -2198,8 +2236,8 @@ msgstr "" msgid "Province/Territory" msgstr "" -#: src/components/MainLayout/index.tsx:140 -#: src/components/MainLayout/index.tsx:318 +#: src/components/MainLayout/index.tsx:142 +#: src/components/MainLayout/index.tsx:320 msgid "Provincial" msgstr "" @@ -2217,7 +2255,7 @@ msgstr "SPAC a dépensé 8,3 milliards de dollars au cours de l'exercice 2024. C #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:379 +#: src/components/JurisdictionPageContent.tsx:364 msgid "Public Accounts of {0} FY {1}" msgstr "" @@ -2263,7 +2301,7 @@ msgstr "Sécurité publique Canada a dépensé 13,9 milliards de dollars au cour msgid "Public Safety, Democratic Institutions and Intergovernmental Affairs Canada (Public Safety Canada) is the federal department responsible for national security, emergency preparedness, and community safety. Established in 2003, it consolidates security, law enforcement, and emergency management functions. It includes the RCMP, CSIS, and CBSA and coordinates federal responses to threats and develops policies on crime prevention, cyber resilience, and disaster preparedness while overseeing intelligence-sharing with domestic and international partners." msgstr "Sécurité publique, Institutions démocratiques et Affaires intergouvernementales Canada (Sécurité publique Canada) est le ministère fédéral responsable de la sécurité nationale, de la préparation aux urgences et de la sécurité communautaire. Établi en 2003, il regroupe les fonctions de sécurité, d'application de la loi et de gestion des urgences. Il comprend la GRC, le SCRS et l'ASFC et coordonne les réponses fédérales aux menaces et élabore des politiques sur la prévention du crime, la cyber-résilience et la préparation aux catastrophes tout en supervisant le partage de renseignements avec les partenaires nationaux et internationaux." -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:363 +#: src/components/JurisdictionPageContent.tsx:348 msgid "Public Service Employees" msgstr "" @@ -2312,7 +2350,7 @@ msgstr "GRC" msgid "Ready Forces" msgstr "Forces prêtes" -#: src/app/[lang]/(main)/budget/page.tsx:278 +#: src/app/[lang]/(main)/federal/budget/page.tsx:288 msgid "Recent developments and their projected impact on the Fall 2025 Budget" msgstr "" @@ -2364,7 +2402,7 @@ msgstr "Prestations de retraite" msgid "Return on Investments" msgstr "Rendement des investissements" -#: src/app/[lang]/(main)/budget/page.tsx:211 +#: src/app/[lang]/(main)/federal/budget/page.tsx:221 #: src/components/Sankey/BudgetSankey.tsx:456 #: src/components/Sankey/index.tsx:700 msgid "Revenue" @@ -2378,15 +2416,15 @@ msgstr "Revenu Canada" msgid "Safety" msgstr "Sécurité" -#: src/app/[lang]/(main)/spending/page.tsx:238 +#: src/app/[lang]/(main)/federal/spending/page.tsx:247 msgid "Salary Distribution" msgstr "" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:134 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:134 msgid "Salary Distribution - {selectedYear} ({selectedGroup})" msgstr "Répartition des salaires - {selectedYear} ({selectedGroup})" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:152 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:152 msgid "Salary Range" msgstr "Échelle des salaires" @@ -2410,6 +2448,10 @@ msgstr "TPS Saskatchewan" msgid "See how Canada's government spends tax dollars—track workforce data, spending trends, and federal debt with clear, non-partisan insights." msgstr "Voyez comment le gouvernement du Canada dépense les dollars des contribuables—suivez les données sur la main-d'œuvre, les tendances des dépenses et la dette fédérale avec des analyses claires et non partisanes." +#: src/app/[lang]/(main)/tax-visualizer/layout.tsx:17 +msgid "See how your taxes are spent across federal and provincial programs. Interactive calculator for Canadian taxpayers." +msgstr "" + #: src/app/[lang]/(main)/spending/employment-and-social-development-canada/page.tsx:204 msgid "Service Canada: responsible for processing EI, CPP, and OAS benefits." msgstr "Service Canada : responsable du traitement des prestations d'AE, du RPC et de la SV." @@ -2448,22 +2490,22 @@ msgstr "Sécurité sociale" msgid "Social Transfer to Provinces" msgstr "Transfert social aux provinces" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:327 +#: src/components/JurisdictionPageContent.tsx:315 msgid "Source" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:251 +#: src/app/[lang]/(main)/federal/spending/page.tsx:260 msgid "Source:" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:428 -#: src/app/[lang]/(main)/budget/page.tsx:294 -#: src/app/[lang]/(main)/spending/page.tsx:266 +#: src/app/[lang]/(main)/federal/budget/page.tsx:304 +#: src/app/[lang]/(main)/federal/spending/page.tsx:275 +#: src/components/JurisdictionPageContent.tsx:414 msgid "Sources" msgstr "Sources" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:377 -#: src/app/[lang]/(main)/spending/page.tsx:226 +#: src/app/[lang]/(main)/federal/spending/page.tsx:235 +#: src/components/JurisdictionPageContent.tsx:362 msgid "Sources:" msgstr "Sources :" @@ -2476,8 +2518,8 @@ msgstr "Espace" msgid "Spending" msgstr "Dépenses" -#: src/components/MainLayout/index.tsx:218 -#: src/components/MainLayout/index.tsx:370 +#: src/components/MainLayout/index.tsx:220 +#: src/components/MainLayout/index.tsx:372 msgid "Spending Database" msgstr "" @@ -2501,7 +2543,7 @@ msgstr "Statistique Canada" msgid "Stay connected by following us on" msgstr "" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:9 +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:9 msgid "Student + Casual" msgstr "Étudiant + Occasionnel" @@ -2525,7 +2567,7 @@ msgstr "Soutien à la compétition mondiale" msgid "Support for Veterans" msgstr "Soutien aux anciens combattants" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:152 +#: src/components/JurisdictionPageContent.tsx:140 msgid "Surplus/Deficit" msgstr "" @@ -2533,15 +2575,19 @@ msgstr "" msgid "Sustainable Bases, IT Systems, Infrastructure" msgstr "Bases durables, systèmes TI, infrastructure" -#: src/components/MainLayout/index.tsx:363 +#: src/components/MainLayout/index.tsx:365 msgid "Tax Calculator" msgstr "" -#: src/components/MainLayout/index.tsx:212 +#: src/components/MainLayout/index.tsx:214 msgid "Tax Visualizer" msgstr "" -#: src/app/[lang]/(main)/spending/TenureChart.tsx:10 +#: src/app/[lang]/(main)/tax-visualizer/layout.tsx:16 +msgid "Tax Visualizer | Canada Spends" +msgstr "" + +#: src/app/[lang]/(main)/federal/spending/TenureChart.tsx:10 msgid "Term" msgstr "Durée déterminée" @@ -2549,7 +2595,7 @@ msgstr "Durée déterminée" msgid "Territorial Formula Financing" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:199 +#: src/app/[lang]/(main)/federal/spending/page.tsx:208 msgid "The average employee is 43.3 years old" msgstr "L'âge moyen des employés est de 43,3 ans" @@ -2697,7 +2743,7 @@ msgstr "Les ministres travaillent aux côtés du sous-ministre des Affaires étr msgid "The Public Services and Procurement Canada (PSPC) is the federal department responsible for providing centralized procurement, real estate management, pay and pension administration for federal employees, and translation services to the Government of Canada. It ensures that government departments have the goods, services, and infrastructure they need to operate efficiently while maintaining transparency, fairness, and value for taxpayers." msgstr "Services publics et Approvisionnement Canada (SPAC) est le ministère fédéral responsable de l'approvisionnement centralisé, de la gestion immobilière, de l'administration de la paye et des pensions pour les employés fédéraux, et des services de traduction pour le gouvernement du Canada. Il s'assure que les ministères gouvernementaux disposent des biens, des services et de l'infrastructure dont ils ont besoin pour fonctionner efficacement tout en maintenant la transparence, l'équité et la valeur pour les contribuables." -#: src/app/[lang]/(main)/budget/page.tsx:184 +#: src/app/[lang]/(main)/federal/budget/page.tsx:194 msgid "The values you see here are based on the FY 2024 Budget with preliminary updates based on government announcements, memos, and leaks, and are meant to provide a rough idea of the budget. Once the official Fall 2025 Budget is released on November 4th, we will update this page to reflect the official budget." msgstr "" @@ -2712,7 +2758,7 @@ msgstr "" msgid "These Ministers are some of the <0>cabinet members who serve at the Prime Minister's discretion. Their tenure typically ends when they resign, are replaced, or when a new Prime Minister takes office and appoints a new cabinet. Outgoing ministers remain in their roles until their successors are sworn in." msgstr "Ces ministres sont parmi les <0>membres du cabinet qui servent à la discrétion du premier ministre. Leur mandat se termine généralement lorsqu'ils démissionnent, sont remplacés ou lorsqu'un nouveau premier ministre prend ses fonctions et nomme un nouveau cabinet. Les ministres sortants restent en fonction jusqu'à ce que leurs successeurs soient assermentés." -#: src/app/[lang]/(main)/budget/page.tsx:177 +#: src/app/[lang]/(main)/federal/budget/page.tsx:187 msgid "This page presents the official Fall 2025 Federal Budget as released by the Government of Canada on November 4th, 2025. All data is sourced directly from official government publications and public accounts from the Government of Canada." msgstr "" @@ -2728,33 +2774,33 @@ msgstr "Par l'intermédiaire de l'Agence de la santé publique du Canada (ASPC), msgid "to help us support our mission." msgstr "" -#: src/app/[lang]/(main)/budget/page.tsx:202 +#: src/app/[lang]/(main)/federal/budget/page.tsx:212 msgid "Total Budget" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:217 +#: src/components/JurisdictionPageContent.tsx:205 msgid "Total Debt" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:162 +#: src/app/[lang]/(main)/federal/spending/page.tsx:171 msgid "Total full-time equivalents" msgstr "Équivalents temps plein totaux" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:165 +#: src/components/JurisdictionPageContent.tsx:153 msgid "Total Revenue" msgstr "" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:172 +#: src/components/JurisdictionPageContent.tsx:160 msgid "Total revenue in {0}" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:178 +#: src/components/JurisdictionPageContent.tsx:166 msgid "Total Spending" msgstr "" #. placeholder {0}: jurisdiction.financialYear -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:186 +#: src/components/JurisdictionPageContent.tsx:174 msgid "Total spending in {0}" msgstr "" @@ -2762,7 +2808,7 @@ msgstr "" msgid "Total Tax" msgstr "" -#: src/app/[lang]/(main)/spending/page.tsx:174 +#: src/app/[lang]/(main)/federal/spending/page.tsx:183 msgid "Total Wages" msgstr "Salaires totaux" @@ -2839,13 +2885,13 @@ msgstr "Transport + Communication" msgid "Transportation and Communication" msgstr "Transport et Communication" -#: src/app/[lang]/(main)/spending/page.tsx:232 -#: src/app/[lang]/(main)/spending/page.tsx:253 +#: src/app/[lang]/(main)/federal/spending/page.tsx:241 +#: src/app/[lang]/(main)/federal/spending/page.tsx:262 #: src/components/Sankey/index.tsx:346 msgid "Treasury Board" msgstr "Conseil du Trésor" -#: src/app/[lang]/(main)/spending/page.tsx:182 +#: src/app/[lang]/(main)/federal/spending/page.tsx:191 msgid "Type of Tenure" msgstr "Type d'emploi" @@ -2894,13 +2940,13 @@ msgstr "Anciens Combattants Canada (ACC) est dirigé par le ministre des Anciens msgid "Veterans Affairs Canada | Canada Spends" msgstr "Anciens Combattants Canada | Canada Dépense" -#: src/app/[lang]/(main)/budget/page.tsx:164 +#: src/app/[lang]/(main)/federal/budget/page.tsx:174 msgid "View Federal 2025 Budget Details" msgstr "" -#: src/app/[lang]/(main)/[jurisdiction]/page.tsx:338 -#: src/app/[lang]/(main)/budget/page.tsx:269 -#: src/app/[lang]/(main)/spending/page.tsx:149 +#: src/app/[lang]/(main)/federal/budget/page.tsx:279 +#: src/app/[lang]/(main)/federal/spending/page.tsx:158 +#: src/components/JurisdictionPageContent.tsx:323 msgid "View this chart in full screen" msgstr "Voir ce graphique en plein écran" @@ -2909,7 +2955,8 @@ msgid "Visitors, International Students + Temporary Workers" msgstr "Visiteurs, étudiants internationaux + travailleurs temporaires" #. placeholder {0}: department.name -#: src/app/[lang]/(main)/[jurisdiction]/[department]/page.tsx:122 +#: src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/departments/[department]/page.tsx:141 +#: src/app/[lang]/(main)/provincial/[province]/[year]/departments/[department]/page.tsx:126 msgid "was spent by {0}" msgstr "" @@ -3102,7 +3149,7 @@ msgstr "Qui dirige Anciens Combattants Canada (ACC)?" msgid "Why did you start this project?" msgstr "Pourquoi avez-vous commencé ce projet?" -#: src/app/[lang]/(main)/spending/SalaryDistributionChart.tsx:80 +#: src/app/[lang]/(main)/federal/spending/SalaryDistributionChart.tsx:80 msgid "Year" msgstr "Année"