diff --git a/README.md b/README.md index c8583599..3b3b38eb 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ pnpm install pnpm run dev ``` +Municipal financial statements are loaded server-side from York Factory. Set +`YORK_FACTORY_API_URL` to override the default production API base (for example, +run Rails with `bin/rails server -p 3001`, then set +`YORK_FACTORY_API_URL=http://localhost:3001/api/v1` when starting Canada Spends). + ## Linting This project uses ESLint with Next.js configuration. Run linting with: diff --git a/package.json b/package.json index a8c673bc..169a9719 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "private": true, "scripts": { "dev": "next dev --turbopack", + "dev:municipal": "YORK_FACTORY_API_URL=http://127.0.0.1:3001/api/v1 next dev --turbopack", "dev:ntp": "next dev", "build": "pnpm extract && pnpm generate-statics && next build", "generate-statics": "tsx scripts/generate-statics.ts", "start": "next start", + "test": "vitest run", "lint": "next lint", "lint:fix": "next lint --fix", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\"", @@ -91,7 +93,8 @@ "simple-git-hooks": "^2.13.1", "tailwindcss": "^4.1.12", "tsx": "^4.20.4", - "typescript": "^5.9.2" + "typescript": "^5.9.2", + "vitest": "3.2.4" }, "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15e9a394..c5a844a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,9 @@ importers: typescript: specifier: ^5.9.2 version: 5.9.2 + vitest: + specifier: 3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1) packages: @@ -449,156 +452,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.9': resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.9': resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.9': resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.9': resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.9': resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.9': resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.9': resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.9': resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.9': resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.9': resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.9': resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.9': resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.9': resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.9': resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.9': resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.9': resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.9': resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.9': resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.9': resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.9': resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.9': resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.9': resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1054,6 +1213,12 @@ packages: '@types/react': optional: true + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -1510,6 +1675,131 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rollup/rollup-android-arm-eabi@4.63.1': + resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.63.1': + resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.63.1': + resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.63.1': + resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.63.1': + resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.63.1': + resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.63.1': + resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.63.1': + resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.63.1': + resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.63.1': + resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.63.1': + resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.63.1': + resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.63.1': + resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.63.1': + resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1639,6 +1929,9 @@ packages: '@tybys/wasm-util@0.10.0': resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -1735,6 +2028,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/dom-speech-recognition@0.0.1': resolution: {integrity: sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==} @@ -1750,6 +2046,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -1986,6 +2285,38 @@ packages: cpu: [x64] os: [win32] + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -2161,6 +2492,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -2243,6 +2578,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2269,6 +2608,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2289,6 +2632,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.5.1: resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==} engines: {node: '>= 8.10.0'} @@ -2580,6 +2927,10 @@ packages: decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2709,6 +3060,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2887,6 +3243,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -2929,6 +3289,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fflate@0.4.8: resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} @@ -3378,6 +3747,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -3560,6 +3932,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.1.0: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} @@ -3944,6 +4319,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3955,6 +4337,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -4262,6 +4648,11 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.63.1: + resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4355,6 +4746,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4413,6 +4807,12 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -4482,6 +4882,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + style-to-js@1.1.18: resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==} @@ -4563,10 +4966,32 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4744,6 +5169,79 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + watchpack@2.4.4: resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} @@ -4804,6 +5302,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -5154,81 +5657,159 @@ snapshots: '@esbuild/aix-ppc64@0.25.9': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.25.9': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.25.9': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.25.9': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.25.9': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.25.9': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.25.9': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.25.9': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.25.9': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.25.9': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.25.9': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.25.9': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.25.9': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.25.9': optional: true + '@esbuild/linux-ppc64@0.28.2': + optional: true + '@esbuild/linux-riscv64@0.25.9': optional: true + '@esbuild/linux-riscv64@0.28.2': + optional: true + '@esbuild/linux-s390x@0.25.9': optional: true + '@esbuild/linux-s390x@0.28.2': + optional: true + '@esbuild/linux-x64@0.25.9': optional: true + '@esbuild/linux-x64@0.28.2': + optional: true + '@esbuild/netbsd-arm64@0.25.9': optional: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true + '@esbuild/netbsd-x64@0.25.9': optional: true + '@esbuild/netbsd-x64@0.28.2': + optional: true + '@esbuild/openbsd-arm64@0.25.9': optional: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true + '@esbuild/openbsd-x64@0.25.9': optional: true + '@esbuild/openbsd-x64@0.28.2': + optional: true + '@esbuild/openharmony-arm64@0.25.9': optional: true + '@esbuild/openharmony-arm64@0.28.2': + optional: true + '@esbuild/sunos-x64@0.25.9': optional: true + '@esbuild/sunos-x64@0.28.2': + optional: true + '@esbuild/win32-arm64@0.25.9': optional: true + '@esbuild/win32-arm64@0.28.2': + optional: true + '@esbuild/win32-ia32@0.25.9': optional: true + '@esbuild/win32-ia32@0.28.2': + optional: true + '@esbuild/win32-x64@0.25.9': optional: true + '@esbuild/win32-x64@0.28.2': + optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))': dependencies: eslint: 9.33.0(jiti@2.5.1) @@ -5717,6 +6298,9 @@ snapshots: optionalDependencies: '@types/react': 19.1.10 + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.4.5 @@ -6158,6 +6742,81 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@rollup/rollup-android-arm-eabi@4.63.1': + optional: true + + '@rollup/rollup-android-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-x64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.63.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.63.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.63.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.63.1': + optional: true + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.12.0': {} @@ -6283,6 +6942,11 @@ snapshots: tslib: 2.8.1 optional: true + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/d3-array@3.2.1': {} '@types/d3-axis@3.0.6': @@ -6404,6 +7068,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/dom-speech-recognition@0.0.1': {} '@types/eslint-scope@3.7.7': @@ -6422,6 +7088,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} '@types/google.maps@3.58.1': {} @@ -6646,6 +7314,52 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -6887,6 +7601,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} astring@1.9.0: {} @@ -6973,6 +7689,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6998,6 +7716,14 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -7013,6 +7739,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} + chokidar@3.5.1: dependencies: anymatch: 3.1.3 @@ -7317,6 +8045,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} defaults@1.0.4: @@ -7539,6 +8269,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.9 '@esbuild/win32-x64': 0.25.9 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -7794,6 +8553,8 @@ snapshots: events@3.3.0: {} + expect-type@1.4.0: {} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -7834,6 +8595,14 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + fflate@0.4.8: {} file-entry-cache@8.0.0: @@ -8362,6 +9131,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -8523,6 +9294,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@11.1.0: {} lru-cache@5.1.1: @@ -9095,12 +9868,18 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} + picomatch@4.0.7: {} + pidtree@0.6.0: {} pofile@1.1.4: {} @@ -9484,6 +10263,38 @@ snapshots: robust-predicates@3.0.2: {} + rollup@4.63.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.63.1 + '@rollup/rollup-android-arm64': 4.63.1 + '@rollup/rollup-darwin-arm64': 4.63.1 + '@rollup/rollup-darwin-x64': 4.63.1 + '@rollup/rollup-freebsd-arm64': 4.63.1 + '@rollup/rollup-freebsd-x64': 4.63.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.1 + '@rollup/rollup-linux-arm-musleabihf': 4.63.1 + '@rollup/rollup-linux-arm64-gnu': 4.63.1 + '@rollup/rollup-linux-arm64-musl': 4.63.1 + '@rollup/rollup-linux-loong64-gnu': 4.63.1 + '@rollup/rollup-linux-loong64-musl': 4.63.1 + '@rollup/rollup-linux-ppc64-gnu': 4.63.1 + '@rollup/rollup-linux-ppc64-musl': 4.63.1 + '@rollup/rollup-linux-riscv64-gnu': 4.63.1 + '@rollup/rollup-linux-riscv64-musl': 4.63.1 + '@rollup/rollup-linux-s390x-gnu': 4.63.1 + '@rollup/rollup-linux-x64-gnu': 4.63.1 + '@rollup/rollup-linux-x64-musl': 4.63.1 + '@rollup/rollup-openbsd-x64': 4.63.1 + '@rollup/rollup-openharmony-arm64': 4.63.1 + '@rollup/rollup-win32-arm64-msvc': 4.63.1 + '@rollup/rollup-win32-ia32-msvc': 4.63.1 + '@rollup/rollup-win32-x64-gnu': 4.63.1 + '@rollup/rollup-win32-x64-msvc': 4.63.1 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -9636,6 +10447,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -9680,6 +10493,10 @@ snapshots: stable-hash@0.0.5: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -9778,6 +10595,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + style-to-js@1.1.18: dependencies: style-to-object: 1.0.11 @@ -9845,11 +10666,26 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -10093,6 +10929,86 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-node@3.2.4(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.63.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 20.19.10 + fsevents: 2.3.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + terser: 5.44.1 + tsx: 4.20.4 + yaml: 2.8.1 + + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.1 + expect-type: 1.4.0 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.6(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@20.19.10)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.4)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.19.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + watchpack@2.4.4: dependencies: glob-to-regexp: 0.4.1 @@ -10198,6 +11114,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: diff --git a/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx index 5d871937..b541215b 100644 --- a/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx +++ b/src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx @@ -1,15 +1,12 @@ -import { JurisdictionPageContent } from "@/components/JurisdictionPageContent"; -import { initLingui } from "@/initLingui"; +import { MunicipalYearPageContent } from "@/components/municipal/MunicipalYearPageContent"; import { - getExpandedDepartments, - getJurisdictionData, getMunicipalitiesByProvince, getAvailableYearsForJurisdiction, } from "@/lib/jurisdictions"; import { locales } from "@/lib/constants"; -import { notFound } from "next/navigation"; -export const dynamicParams = false; +export const dynamicParams = true; +export const dynamic = "force-dynamic"; export async function generateStaticParams() { const municipalitiesByProvince = getMunicipalitiesByProvince(); @@ -32,45 +29,6 @@ export async function generateStaticParams() { 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, }: { diff --git a/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx b/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx index b8fa9a57..2f8cf74e 100644 --- a/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx +++ b/src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx @@ -1,15 +1,16 @@ -import { MunicipalYearPageContent } from "@/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page"; -import { BASE_URL } from "@/lib/constants"; +import { getMunicipalitiesByProvince } from "@/lib/jurisdictions"; +import { getAvailableYearsForJurisdiction } from "@/lib/jurisdictions"; +import { BASE_URL, locales } from "@/lib/constants"; import { - getMunicipalitiesByProvince, - getAvailableYearsForJurisdiction, -} from "@/lib/jurisdictions"; -import { locales } from "@/lib/constants"; + getMunicipalityFinancialStatements, + latestMunicipalFinancialYear, +} from "@/lib/municipal-financial-statements"; +import { notFound, redirect } from "next/navigation"; +import type { Metadata } from "next"; import { generateHreflangAlternates } from "@/lib/utils"; -import { notFound } from "next/navigation"; -import { Metadata } from "next"; -export const dynamicParams = false; +export const dynamicParams = true; +export const dynamic = "force-dynamic"; export async function generateStaticParams() { const municipalitiesByProvince = getMunicipalitiesByProvince(); @@ -25,27 +26,36 @@ export async function generateStaticParams() { ); } +async function latestYearFor(province: string, municipality: string) { + try { + const statements = await getMunicipalityFinancialStatements( + province, + municipality, + ); + return latestMunicipalFinancialYear(statements); + } catch { + const years = getAvailableYearsForJurisdiction( + `${province}/${municipality}`, + ); + return years.length ? Math.max(...years.map(Number)) : null; + } +} + 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 {}; - } + const latestYear = await latestYearFor(province, municipality); + 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]`, + "/municipal/[province]/[municipality]", { province, municipality }, ), canonical, @@ -59,24 +69,10 @@ export default async function MunicipalPage({ params: Promise<{ province: string; municipality: string; lang: string }>; }) { const { province, municipality, lang } = await params; + const latestYear = await latestYearFor(province, municipality); + if (!latestYear) notFound(); - 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 ( - - ); + // This target advances whenever a newer statement is reviewed. Keep the + // redirect temporary so browsers and CDNs cannot pin the yearless URL. + redirect(`/${lang}/municipal/${province}/${municipality}/${latestYear}`); } diff --git a/src/app/[lang]/(main)/municipal/page.tsx b/src/app/[lang]/(main)/municipal/page.tsx new file mode 100644 index 00000000..378cd897 --- /dev/null +++ b/src/app/[lang]/(main)/municipal/page.tsx @@ -0,0 +1,59 @@ +import type { Metadata } from "next"; +import { Trans, useLingui } from "@lingui/react/macro"; +import { H1, Intro, Page, PageContent, Section } from "@/components/Layout"; +import { MunicipalFinancialStatementsSearch } from "@/components/municipal/MunicipalFinancialStatementsSearch"; +import { initLingui } from "@/initLingui"; +import { getMunicipalFinancialStatements } from "@/lib/municipal-financial-statements"; +import { generateHreflangAlternates } from "@/lib/utils"; + +export const dynamic = "force-dynamic"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string }>; +}): Promise { + const { lang } = await params; + initLingui(lang); + // eslint-disable-next-line react-hooks/rules-of-hooks + const { t } = useLingui(); + return { + title: t`Municipal Financial Statements | Canada Spends`, + description: t`Explore reviewed financial statement data for municipalities across Canada.`, + alternates: generateHreflangAlternates(lang, "/municipal"), + }; +} + +export default async function MunicipalFinancialStatementsPage({ + params, +}: { + params: Promise<{ lang: string }>; +}) { + const { lang } = await params; + initLingui(lang); + const municipalities = await getMunicipalFinancialStatements(); + + return ( + + +
+

+ Municipal Financial Statements +

+ + + Explore revenue, expenses, assets, liabilities, and accumulated + surplus reported by municipalities across Canada. + + +
+
+ +
+
+
+ ); +} diff --git a/src/components/FinancialYearSelector.tsx b/src/components/FinancialYearSelector.tsx new file mode 100644 index 00000000..3b0cb96e --- /dev/null +++ b/src/components/FinancialYearSelector.tsx @@ -0,0 +1,45 @@ +"use client"; + +import Link from "next/link"; +import { Trans } from "@lingui/react/macro"; + +export function FinancialYearSelector({ + basePath, + currentYear, + availableYears, + formatYear, +}: { + basePath: string; + currentYear: string; + availableYears: string[]; + formatYear?: (year: string) => string; +}) { + if (availableYears.length <= 1) return null; + + return ( +
+

+ View Other Fiscal Years +

+
+ {availableYears.map((year) => { + const current = year === currentYear; + return ( + + {formatYear ? formatYear(year) : FY {year}} + + ); + })} +
+
+ ); +} diff --git a/src/components/HeroButtons.tsx b/src/components/HeroButtons.tsx index 47e8b255..3c077941 100644 --- a/src/components/HeroButtons.tsx +++ b/src/components/HeroButtons.tsx @@ -79,6 +79,14 @@ export function HeroButtons({ className="bg-popover text-popover-foreground rounded-md shadow-lg p-1 flex flex-col min-w-50 z-200 max-h-80 overflow-y-auto" sideOffset={4} > + + + Browse all financial statements + + {municipalitiesByProvince.map(({ province, municipalities }) => (
diff --git a/src/components/MainLayout/_components/DesktopNav.tsx b/src/components/MainLayout/_components/DesktopNav.tsx index 6050028c..46c17605 100644 --- a/src/components/MainLayout/_components/DesktopNav.tsx +++ b/src/components/MainLayout/_components/DesktopNav.tsx @@ -140,6 +140,14 @@ export default function DesktopNav(props: DesktopNavProps) { className="bg-popover rounded-md shadow-lg p-1 flex flex-col min-w-50 z-200" sideOffset={8} > + + + All financial statements + + {municipalitiesByProvince.map( ({ province, municipalities }) => ( diff --git a/src/components/MainLayout/_components/MobileMenu.tsx b/src/components/MainLayout/_components/MobileMenu.tsx index a7b8d09f..85341ace 100644 --- a/src/components/MainLayout/_components/MobileMenu.tsx +++ b/src/components/MainLayout/_components/MobileMenu.tsx @@ -123,6 +123,15 @@ export function MobileMenu(props: MobileMenuProps) {

Municipal

+ setIsMenuOpen(false)} + > + + All financial statements + + {municipalitiesByProvince.map(({ province, municipalities }) => (

diff --git a/src/components/Sankey/SankeyChart.tsx b/src/components/Sankey/SankeyChart.tsx index 188d6f80..9e8886d5 100644 --- a/src/components/Sankey/SankeyChart.tsx +++ b/src/components/Sankey/SankeyChart.tsx @@ -113,6 +113,7 @@ export function SankeyChart(props: SankeyChartProps) { ); const [searchResult, setSearchResult] = useState(null); const [hoverNode, setHoverNode] = useState(null); + const [flowTotals, setFlowTotals] = useState({ revenue: 0, spending: 0 }); // Mouse position as fallback - ensures tooltip still works if blockRect is missing const [mousePosition, setMousePosition] = useState<{ x: number; @@ -133,6 +134,7 @@ export function SankeyChart(props: SankeyChartProps) { const { nodes, revenueTotal, spendingTotal } = getFlatData(transformedData); setFlatData(nodes); + setFlowTotals({ revenue: revenueTotal, spending: spendingTotal }); setTotalAmount(Math.max(revenueTotal, spendingTotal)); }, [props.data]); @@ -299,7 +301,9 @@ export function SankeyChart(props: SankeyChartProps) { } height={chartHeight} amountScalingFactor={amountScalingFactor} - onMouseOver={handleMouseOver(chartData.revenue)} + onMouseOver={handleMouseOver( + chartData.revenue || flowTotals.revenue, + )} onMouseOut={handleMouseOut} />

diff --git a/src/components/Sankey/utils.ts b/src/components/Sankey/utils.ts index 68d12359..07c1a98e 100644 --- a/src/components/Sankey/utils.ts +++ b/src/components/Sankey/utils.ts @@ -1,18 +1,15 @@ import { SankeyNode } from "./SankeyChartD3"; +import { formatCompactCurrency } from "@/lib/format-compact-currency"; export const formatNumber = (amount: number, scalingFactor = 1e9) => { - return Intl.NumberFormat("en-US", { - style: "currency", + const scaledAmount = Number(amount * scalingFactor); + + return formatCompactCurrency(scaledAmount, { + locale: "en-US", currency: "USD", - notation: "compact", maximumFractionDigits: - Math.abs(amount * scalingFactor) >= 1e9 - ? 2 - : Math.abs(amount * scalingFactor) >= 1e6 - ? 1 - : 0, - minimumFractionDigits: 0, - }).format(Number(amount * scalingFactor)); + Math.abs(scaledAmount) >= 1e9 ? 2 : Math.abs(scaledAmount) >= 1e6 ? 1 : 0, + }); }; // Generate a unique ID from a name string diff --git a/src/components/first-nations/FirstNationsYearSelector.tsx b/src/components/first-nations/FirstNationsYearSelector.tsx index 6a5b04cd..056333ea 100644 --- a/src/components/first-nations/FirstNationsYearSelector.tsx +++ b/src/components/first-nations/FirstNationsYearSelector.tsx @@ -1,7 +1,6 @@ "use client"; -import Link from "next/link"; -import { Trans } from "@lingui/react/macro"; +import { FinancialYearSelector } from "@/components/FinancialYearSelector"; interface FirstNationsYearSelectorProps { bcid: string; @@ -16,56 +15,23 @@ export function FirstNationsYearSelector({ availableYears, lang, }: FirstNationsYearSelectorProps) { - if (availableYears.length <= 1) { - return null; - } - return ( -
-

- View Other Fiscal Years -

-
- {availableYears.map((year) => { - const isCurrentYear = year === currentYear; - return ( - - {formatFiscalYear(year)} - - ); - })} -
-
+ ); } function formatFiscalYear(year: string): string { - // If year is in format "2024-03-31", show "FY 2023-24" - // If year is just "2024", show "FY 2024" - if (year.includes("-")) { - const date = new Date(year); - const endYear = date.getFullYear(); - const month = date.getMonth(); // 0-indexed - - // If fiscal year ends in early months (Jan-Mar), it's for the previous calendar year - if (month <= 2) { - // January = 0, February = 1, March = 2 - const startYear = endYear - 1; - return `FY ${startYear}-${String(endYear).slice(-2)}`; - } else { - const nextYear = endYear + 1; - return `FY ${endYear}-${String(nextYear).slice(-2)}`; - } - } + if (!year.includes("-")) return `FY ${year}`; - return `FY ${year}`; + const date = new Date(`${year}T00:00:00Z`); + const endYear = date.getUTCFullYear(); + const month = date.getUTCMonth(); + return month <= 2 + ? `FY ${endYear - 1}-${String(endYear).slice(-2)}` + : `FY ${endYear}-${String(endYear + 1).slice(-2)}`; } diff --git a/src/components/municipal/MunicipalFinancialStatementContent.tsx b/src/components/municipal/MunicipalFinancialStatementContent.tsx new file mode 100644 index 00000000..8ac9c2cf --- /dev/null +++ b/src/components/municipal/MunicipalFinancialStatementContent.tsx @@ -0,0 +1,694 @@ +"use client"; + +import { useState } from "react"; +import { Trans } from "@lingui/react/macro"; +import { FinancialYearSelector } from "@/components/FinancialYearSelector"; +import { + H1, + H2, + Intro, + P, + Page, + PageContent, + Section, +} from "@/components/Layout"; +import { JurisdictionSankey } from "@/components/Sankey/JurisdictionSankey"; +import { Tooltip } from "@/components/Tooltip"; +import { + SourceDocumentIcon, + SourceDocumentViewer, +} from "@/components/first-nations/SourceDocumentViewer"; +import type { + MunicipalFinancialFact, + MunicipalStatement, + MunicipalityFinancialDetail, +} from "@/lib/municipal-financial-statements"; +import { formatCompactCurrency } from "@/lib/format-compact-currency"; + +function conceptLabel(concept: MunicipalFinancialFact["concept"]) { + switch (concept) { + case "total_financial_assets": + return Total financial assets; + case "total_liabilities": + return Total liabilities; + case "net_financial_assets": + return Net financial assets (debt); + case "total_non_financial_assets": + return Total non-financial assets; + case "accumulated_surplus": + return Accumulated surplus; + case "opening_accumulated_surplus": + return Opening accumulated surplus; + case "total_revenue": + return Total revenue; + case "total_expenses": + return Total expenses; + case "annual_surplus": + return Annual surplus (deficit); + } +} + +function formatCurrency(value: number, lang: string): string { + return formatCompactCurrency(value, { + locale: lang === "fr" ? "fr-CA" : "en-CA", + }); +} + +function formatCurrencyExact(value: number, lang: string): string { + return new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", { + style: "currency", + currency: "CAD", + maximumFractionDigits: 0, + }).format(value); +} + +function formatNumber(value: number, lang: string, maximumFractionDigits = 1) { + return new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", { + maximumFractionDigits, + }).format(value); +} + +const HelpIcon = () => ( + + + + + +); + +function SummaryCard({ + label, + value, + description, + help, +}: { + label: React.ReactNode; + value: string; + description: React.ReactNode; + help: React.ReactNode; +}) { + return ( +
+
+ {label} + + + +
+
{value}
+
{description}
+
+ ); +} + +function factFor( + statement: MunicipalStatement, + concept: MunicipalFinancialFact["concept"], +): MunicipalFinancialFact | undefined { + return statement.facts.find((fact) => fact.concept === concept); +} + +function FactCard({ + fact, + sourceUrl, + lang, +}: { + fact: MunicipalFinancialFact; + sourceUrl: string | null; + lang: string; +}) { + const source = sourceUrl ? `${sourceUrl}#page=${fact.source_page}` : null; + return ( +
+
{conceptLabel(fact.concept)}
+
+ {formatCurrency(fact.value, lang)} +
+
+ Reported as “{fact.raw_label}” + {source && ( + <> + {" · "} + + page {fact.source_page} + + + )} +
+
+ ); +} + +function OperationsComparison({ + statement, + lang, +}: { + statement: MunicipalStatement; + lang: string; +}) { + const revenue = factFor(statement, "total_revenue"); + const expenses = factFor(statement, "total_expenses"); + if (!revenue || !expenses) return null; + const maximum = Math.max( + Math.abs(revenue.value), + Math.abs(expenses.value), + 1, + ); + + return ( +
+ {[ + { + key: "revenue", + label: Revenue, + value: revenue.value, + colour: "bg-emerald-700", + }, + { + key: "expenses", + label: Expenses, + value: expenses.value, + colour: "bg-auburn-700", + }, + ].map(({ key, label, value, colour }) => ( +
+
+ {label} + {formatCurrency(value, lang)} +
+
+
+
+
+ ))} +
+ ); +} + +function VerificationResults({ + statement, + lang, +}: { + statement: MunicipalStatement; + lang: string; +}) { + const { verification } = statement; + if (!verification) return null; + + const reviewedAt = new Intl.DateTimeFormat( + lang === "fr" ? "fr-CA" : "en-CA", + { dateStyle: "long", timeZone: "UTC" }, + ).format(new Date(verification.reviewed_at)); + + return ( +
+
+
+
+ {verification.summary.fail === 0 ? ( + Independent verification completed + ) : ( + Verification requires attention + )} +
+
+ + Reviewed by {verification.reviewed_by} on {reviewedAt} + +
+
+
+ + {verification.summary.pass} passed + + {verification.summary.skip > 0 && ( + + {verification.summary.skip} skipped + + )} + {verification.summary.fail > 0 && ( + + {verification.summary.fail} failed + + )} +
+
+ {verification.review_notes && ( +

+ {verification.review_notes} +

+ )} +
+ + + View all {verification.summary.total} verification checks + + +
+ + + + + + + + + + {verification.checks.map((check, index) => ( + + + + + + ))} + +
+ Result + + Test + + Details +
+ {check.status === "pass" ? ( + Pass + ) : check.status === "skip" ? ( + Skipped + ) : ( + Fail + )} + {check.id}{check.detail}
+
+
+
+ ); +} + +export function MunicipalFinancialStatementContent({ + municipality, + statement, + lang, +}: { + municipality: MunicipalityFinancialDetail; + statement: MunicipalStatement; + lang: string; +}) { + const [showSource, setShowSource] = useState(false); + const sourceUrl = statement.source.download_url || statement.source.page_url; + const fiscalYearEnd = new Intl.DateTimeFormat( + lang === "fr" ? "fr-CA" : "en-CA", + { dateStyle: "long", timeZone: "UTC" }, + ).format(new Date(`${statement.fiscal_year_end}T00:00:00Z`)); + const operations = ["annual_surplus", "total_revenue", "total_expenses"] + .map((concept) => + factFor(statement, concept as MunicipalFinancialFact["concept"]), + ) + .filter((fact): fact is MunicipalFinancialFact => Boolean(fact)); + const position = [ + "total_financial_assets", + "total_liabilities", + "net_financial_assets", + "total_non_financial_assets", + "accumulated_surplus", + ] + .map((concept) => + factFor(statement, concept as MunicipalFinancialFact["concept"]), + ) + .filter((fact): fact is MunicipalFinancialFact => Boolean(fact)); + const revenue = factFor(statement, "total_revenue"); + const expenses = factFor(statement, "total_expenses"); + const surplus = factFor(statement, "annual_surplus"); + const context = municipality.context; + const censusYear = context.census_year; + + return ( + + +
+

{municipality.name}

+ + + Reviewed financial statement data for {municipality.name} in{" "} + {municipality.province_name}, for the fiscal year ending{" "} + {fiscalYearEnd}. + + + {context.population && censusYear && ( +

+ + The municipality serves a {censusYear} Census population of{" "} + {formatNumber(context.population, lang, 0)} across{" "} + {formatNumber(context.area_sq_km || 0, lang)} square kilometres. + +

+ )} +
+ + {statement.sankey && ( + <> +
+

+ + Where the money came from and where it went + setShowSource(!showSource)} + /> + +

+

+ + Revenue sources flow in from the left and spending by service + or function flows out on the right. Revenue offsets and losses + are shown as outflows, while expense recoveries are shown as + inflows. Values come from detailed schedules in the audited + financial statements. + +

+ +
+
+ +
+ + )} + + {statement.line_items.length > 0 && ( +
+

+ Reported revenue and expense details +

+

+ + These are the detailed rows extracted from the audited + statement. This table preserves every signed amount, including + negative and consolidation entries that the Sankey repositions + as offsets or recoveries. + +

+
+ {(["revenue", "expense"] as const).map((flow) => { + const items = statement.line_items.filter( + (item) => item.flow === flow, + ); + if (items.length === 0) return null; + return ( +
+

+ {flow === "revenue" ? ( + Revenue + ) : ( + Expenses + )} +

+
+ + + {items.map((item, index) => ( + + + + + ))} + +
+ + {item.label} + + {sourceUrl && ( + + page {item.source_page} + + )} + + {formatCurrencyExact(item.value, lang)} +
+
+
+ ); + })} +
+
+ )} + + {operations.length > 0 && ( +
+

+ Financial summary for {statement.fiscal_year} +

+

+ + How much the municipality reported earning and spending during + the fiscal year. + +

+
+ {surplus && ( + Annual surplus (deficit)} + value={formatCurrency(surplus.value, lang)} + description={Revenue remaining after expenses} + help={ + + The reported difference between annual revenue and + expenses, including any separately presented adjustments. + + } + /> + )} + {revenue && ( + Total revenue} + value={formatCurrency(revenue.value, lang)} + description={ + statement.per_capita?.total_revenue ? ( + + {formatCurrency( + statement.per_capita.total_revenue, + lang, + )}{" "} + per resident + + ) : ( + All reported municipal revenue + ) + } + help={ + + Taxes, transfers, fees, services, and other reported + income. + + } + /> + )} + {expenses && ( + Total expenses} + value={formatCurrency(expenses.value, lang)} + description={ + statement.per_capita?.total_expenses ? ( + + {formatCurrency( + statement.per_capita.total_expenses, + lang, + )}{" "} + per resident + + ) : ( + All reported municipal expenses + ) + } + help={ + + Program, service, administration, financing, and + amortization expenses. + + } + /> + )} +
+ +
+ )} + + {context.population && censusYear && ( +
+

+ Community context +

+

+ + Census context makes municipal totals easier to compare, while + recognizing that municipal boundaries and service + responsibilities differ. + +

+
+ Population} + value={formatNumber(context.population, lang, 0)} + note={{censusYear} Census} + /> + {context.area_sq_km && ( + Land area} + value={`${formatNumber(context.area_sq_km, lang)} km²`} + note={Municipal census subdivision} + /> + )} + {context.population_density_per_sq_km && ( + Population density} + value={formatNumber( + context.population_density_per_sq_km, + lang, + )} + note={Residents per km²} + /> + )} + {statement.per_capita?.total_expenses && ( + Expenses per resident} + value={formatCurrency( + statement.per_capita.total_expenses, + lang, + )} + note={Financial year {statement.fiscal_year}} + /> + )} +
+
+ )} + + {position.length > 0 && ( +
+

+ Financial position +

+

+ + Assets, liabilities, and accumulated surplus at the end of the + fiscal year. + +

+
+ {position.map((fact) => ( + + ))} +
+
+ )} + +
+ { + const period = municipality.available_periods.find( + (candidate) => String(candidate.year) === year, + ); + return formatFiscalYearLabel(period?.fiscal_year_end ?? year); + }} + /> +
+ +
+

+ Source and methodology +

+

+ + Values are extracted from the municipality’s published financial + statements and independently reviewed before publication. Page + links point to the reported figure in the source document. + +

+ + {sourceUrl && ( + + View source financial statements + + )} +
+
+
+ ); +} + +function FactCardSimple({ + label, + value, + note, +}: { + label: React.ReactNode; + value: string; + note: React.ReactNode; +}) { + return ( +
+
{label}
+
{value}
+
{note}
+
+ ); +} + +function formatFiscalYearLabel(fiscalYearEnd: string): string { + if (!fiscalYearEnd.includes("-")) return `FY ${fiscalYearEnd}`; + + const date = new Date(`${fiscalYearEnd}T00:00:00Z`); + const endYear = date.getUTCFullYear(); + const month = date.getUTCMonth(); + return month <= 2 + ? `FY ${endYear - 1}-${String(endYear).slice(-2)}` + : `FY ${endYear}`; +} diff --git a/src/components/municipal/MunicipalFinancialStatementsSearch.tsx b/src/components/municipal/MunicipalFinancialStatementsSearch.tsx new file mode 100644 index 00000000..434a8fed --- /dev/null +++ b/src/components/municipal/MunicipalFinancialStatementsSearch.tsx @@ -0,0 +1,127 @@ +"use client"; + +import Link from "next/link"; +import { useMemo, useState } from "react"; +import { Plural, Trans, useLingui } from "@lingui/react/macro"; +import type { MunicipalityFinancialSummary } from "@/lib/municipal-financial-statements"; + +export function MunicipalFinancialStatementsSearch({ + municipalities, + lang, +}: { + municipalities: MunicipalityFinancialSummary[]; + lang: string; +}) { + const { t } = useLingui(); + const [query, setQuery] = useState(""); + const [province, setProvince] = useState(""); + const provinces = useMemo( + () => + Array.from( + new Map( + municipalities.map((municipality) => [ + municipality.province, + municipality.province_name, + ]), + ), + ).sort((a, b) => a[1].localeCompare(b[1])), + [municipalities], + ); + const filtered = useMemo(() => { + const normalizedQuery = query.trim().toLocaleLowerCase(); + return municipalities.filter( + (municipality) => + (!province || municipality.province === province) && + (!normalizedQuery || + municipality.name.toLocaleLowerCase().includes(normalizedQuery)), + ); + }, [municipalities, province, query]); + + return ( +
+
+ + +
+ +

+ +

+ +
+ {filtered.map((municipality) => { + const latestYear = Math.max(...municipality.available_years); + const earliestYear = Math.min(...municipality.available_years); + return ( + + + {municipality.name} + + + {municipality.province_name} + {municipality.legal_form ? ` · ${municipality.legal_form}` : ""} + + + {municipality.available_years.length === 1 ? ( + {latestYear} financial statements + ) : ( + <> + {" "} + · {earliestYear}–{latestYear} + + )} + + + ); + })} +
+ + {filtered.length === 0 && ( +

+ No municipalities match those filters. +

+ )} +
+ ); +} diff --git a/src/components/municipal/MunicipalYearPageContent.tsx b/src/components/municipal/MunicipalYearPageContent.tsx new file mode 100644 index 00000000..82552cde --- /dev/null +++ b/src/components/municipal/MunicipalYearPageContent.tsx @@ -0,0 +1,74 @@ +import { JurisdictionPageContent } from "@/components/JurisdictionPageContent"; +import { MunicipalFinancialStatementContent } from "@/components/municipal/MunicipalFinancialStatementContent"; +import { initLingui } from "@/initLingui"; +import { + getAvailableYearsForJurisdiction, + getExpandedDepartments, + getJurisdictionData, +} from "@/lib/jurisdictions"; +import { getMunicipalityFinancialStatements } from "@/lib/municipal-financial-statements"; +import { notFound } from "next/navigation"; + +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}`; + let financialStatements = null; + try { + financialStatements = await getMunicipalityFinancialStatements( + province, + municipality, + year, + ); + } catch { + // Preserve the older curated municipality pages if York is unavailable. + } + const statement = financialStatements?.statements.find( + (candidate) => candidate.fiscal_year === Number(year), + ); + if (financialStatements && statement) { + return ( + + ); + } + + const hasCuratedData = + getAvailableYearsForJurisdiction(jurisdictionSlug).includes(year); + + if (hasCuratedData) { + const { jurisdiction, sankey } = getJurisdictionData( + jurisdictionSlug, + year, + ); + const departments = getExpandedDepartments(jurisdictionSlug, year); + + return ( + + ); + } + + notFound(); +} diff --git a/src/lib/format-compact-currency.test.ts b/src/lib/format-compact-currency.test.ts new file mode 100644 index 00000000..8b0c492b --- /dev/null +++ b/src/lib/format-compact-currency.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; + +import { formatCompactCurrency } from "./format-compact-currency"; + +describe("formatCompactCurrency", () => { + it("pins English Canadian compact currency without trailing zeros", () => { + expect(formatCompactCurrency(7_301_415)).toBe("$7.3M"); + expect(formatCompactCurrency(6_231_372)).toBe("$6.23M"); + expect(formatCompactCurrency(569_000)).toBe("$569K"); + expect(formatCompactCurrency(999_950)).toBe("$999.95K"); + expect(formatCompactCurrency(1_000_000_000)).toBe("$1B"); + expect(formatCompactCurrency(999)).toBe("$999"); + expect(formatCompactCurrency(0)).toBe("$0"); + expect(formatCompactCurrency(-7_301_415)).toBe("-$7.3M"); + }); + + it("pins French Canadian compact currency and sign placement", () => { + const options = { locale: "fr-CA" }; + + expect(formatCompactCurrency(7_301_415, options)).toBe("7,3\u00a0M\u00a0$"); + expect(formatCompactCurrency(569_000, options)).toBe("569\u00a0k\u00a0$"); + expect(formatCompactCurrency(1_000_000_000, options)).toBe( + "1\u00a0G\u00a0$", + ); + expect(formatCompactCurrency(0, options)).toBe("0\u00a0$"); + expect(formatCompactCurrency(-7_301_415, options)).toBe( + "-7,3\u00a0M\u00a0$", + ); + }); + + it("supports the Sankey precision policy", () => { + expect( + formatCompactCurrency(7_301_415, { + currency: "USD", + locale: "en-US", + maximumFractionDigits: 1, + }), + ).toBe("$7.3M"); + }); + + it("promotes values that round across a compact-unit boundary", () => { + expect(formatCompactCurrency(999_999, { maximumFractionDigits: 2 })).toBe( + "$1M", + ); + expect(formatCompactCurrency(999.9, { maximumFractionDigits: 0 })).toBe( + "$1K", + ); + expect( + formatCompactCurrency(999_999_999, { maximumFractionDigits: 1 }), + ).toBe("$1B"); + }); +}); diff --git a/src/lib/format-compact-currency.ts b/src/lib/format-compact-currency.ts new file mode 100644 index 00000000..0dd54689 --- /dev/null +++ b/src/lib/format-compact-currency.ts @@ -0,0 +1,48 @@ +export type CompactCurrencyOptions = { + currency?: string; + locale?: string; + maximumFractionDigits?: number; +}; + +export function formatCompactCurrency( + amount: number, + { + currency = "CAD", + locale = "en-CA", + maximumFractionDigits = 2, + }: CompactCurrencyOptions = {}, +): string { + const french = locale.toLowerCase().startsWith("fr"); + const absoluteAmount = Math.abs(amount); + const units = [ + { threshold: 1, divisor: 1, en: "", fr: "" }, + { threshold: 1e3, divisor: 1e3, en: "K", fr: "k" }, + { threshold: 1e6, divisor: 1e6, en: "M", fr: "M" }, + { threshold: 1e9, divisor: 1e9, en: "B", fr: "G" }, + { threshold: 1e12, divisor: 1e12, en: "T", fr: "T" }, + ]; + let unitIndex = units.findLastIndex( + ({ threshold }) => absoluteAmount >= threshold, + ); + unitIndex = Math.max(unitIndex, 0); + let unit = units[unitIndex]; + let scaledAmount = absoluteAmount / unit.divisor; + while ( + unitIndex < units.length - 1 && + Number(scaledAmount.toFixed(maximumFractionDigits)) >= 1000 + ) { + unit = units[++unitIndex]; + scaledAmount = absoluteAmount / unit.divisor; + } + const number = scaledAmount + .toFixed(maximumFractionDigits) + .replace(/\.0+$|(?<=\.[0-9]*?)0+$/u, "") + .replace(".", french ? "," : "."); + const suffix = french ? unit.fr : unit.en; + const sign = amount < 0 ? "-" : ""; + const symbol = currency === "CAD" || currency === "USD" ? "$" : currency; + + return french + ? `${sign}${number}${suffix ? `\u00a0${suffix}` : ""}\u00a0${symbol}` + : `${sign}${symbol}${number}${suffix}`; +} diff --git a/src/lib/municipal-financial-statements.test.ts b/src/lib/municipal-financial-statements.test.ts new file mode 100644 index 00000000..70af6b87 --- /dev/null +++ b/src/lib/municipal-financial-statements.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; + +import { latestMunicipalFinancialYear } from "./municipal-financial-statements"; + +describe("latestMunicipalFinancialYear", () => { + it("selects the maximum available year without relying on API order", () => { + expect( + latestMunicipalFinancialYear({ available_years: [2024, 2025, 2023] }), + ).toBe(2025); + }); + + it("returns null when no reviewed year is available", () => { + expect(latestMunicipalFinancialYear({ available_years: [] })).toBeNull(); + expect(latestMunicipalFinancialYear(null)).toBeNull(); + }); +}); diff --git a/src/lib/municipal-financial-statements.ts b/src/lib/municipal-financial-statements.ts new file mode 100644 index 00000000..a7606a73 --- /dev/null +++ b/src/lib/municipal-financial-statements.ts @@ -0,0 +1,172 @@ +import type { SankeyData } from "@/components/Sankey/SankeyChartD3"; + +const YORK_FACTORY_API_URL = ( + process.env.YORK_FACTORY_API_URL ?? + "https://yorkfactory.buildcanada.com/api/v1" +).replace(/\/$/, ""); + +export type MunicipalFinancialFact = { + concept: + | "total_financial_assets" + | "total_liabilities" + | "net_financial_assets" + | "total_non_financial_assets" + | "accumulated_surplus" + | "opening_accumulated_surplus" + | "total_revenue" + | "total_expenses" + | "annual_surplus"; + value: number; + raw_label: string; + raw_text: string; + statement: "financial_position" | "operations" | "accumulated_surplus"; + source_page: number; + column_year: string; + confidence: number | null; +}; + +export type MunicipalStatement = { + fiscal_year: number; + fiscal_year_end: string; + statement_basis: "consolidated" | "non_consolidated"; + language: "en" | "fr" | "bilingual" | null; + source: { + document_id: string; + page_url: string | null; + download_url: string | null; + }; + facts: MunicipalFinancialFact[]; + line_items: MunicipalFinancialLineItem[]; + verification?: MunicipalStatementVerification; + per_capita: Partial> | null; + sankey: SankeyData | null; +}; + +export type MunicipalStatementVerification = { + status: "approved"; + reviewed_at: string; + reviewed_by: string; + review_notes: string | null; + summary: { + total: number; + pass: number; + skip: number; + fail: number; + }; + checks: Array<{ + id: string; + status: "pass" | "skip" | "fail"; + detail: string; + }>; +}; + +export type MunicipalFinancialLineItem = { + flow: "revenue" | "expense"; + category: string; + label: string; + value: number; + raw_text: string; + scale: number; + source_page: number; + column_year: string; + position: number; + confidence: number | null; +}; + +export type MunicipalityCensusContext = { + census_year: number | null; + population: number | null; + area_sq_km: number | null; + population_density_per_sq_km: number | null; + geographies: Array<{ + uid: string; + name: string; + population: number | null; + area_sq_km: number | null; + }>; +}; + +export type MunicipalityFinancialSummary = { + canonical_id: string; + slug: string; + province: string; + province_name: string; + province_slug: string; + name: string; + name_fr: string | null; + legal_form: string | null; + website_url: string | null; + available_years: number[]; + available_periods: Array<{ year: number; fiscal_year_end: string }>; +}; + +export type MunicipalityFinancialDetail = MunicipalityFinancialSummary & { + context: MunicipalityCensusContext; + statements: MunicipalStatement[]; +}; + +export function latestMunicipalFinancialYear( + municipality: + | Pick + | null + | undefined, +): number | null { + if (!municipality?.available_years.length) return null; + + return Math.max(...municipality.available_years); +} + +async function yorkFactoryFetch(path: string): Promise { + const response = await fetch(`${YORK_FACTORY_API_URL}${path}`, { + cache: "no-store", + }); + + if (!response.ok) { + throw new YorkFactoryApiError(response.status); + } + + return response.json() as Promise; +} + +class YorkFactoryApiError extends Error { + constructor(readonly status: number) { + super(`York Factory API error: ${status}`); + } +} + +export async function getMunicipalFinancialStatements(): Promise< + MunicipalityFinancialSummary[] +> { + const municipalities: MunicipalityFinancialSummary[] = []; + let page = 1; + let totalPages = 1; + do { + const payload = await yorkFactoryFetch<{ + data: MunicipalityFinancialSummary[]; + meta: { total_pages: number }; + }>(`/warehouse/municipal_financial_statements?page=${page}&per_page=5000`); + municipalities.push(...payload.data); + totalPages = payload.meta.total_pages; + page += 1; + } while (page <= totalPages); + return municipalities; +} + +export async function getMunicipalityFinancialStatements( + province: string, + municipality: string, + year?: string, +): Promise { + const suffix = year ? `/${encodeURIComponent(year)}` : ""; + + try { + return await yorkFactoryFetch( + `/warehouse/municipal_financial_statements/${encodeURIComponent(province)}/${encodeURIComponent(municipality)}${suffix}`, + ); + } catch (error) { + if (error instanceof YorkFactoryApiError && error.status === 404) { + return null; + } + throw error; + } +} diff --git a/src/locales/en.po b/src/locales/en.po index fa68b42e..350b8b1b 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -17,6 +17,16 @@ msgstr "" msgid "— Canada Spends Team" msgstr "— Canada Spends Team" +#. placeholder {0}: filtered.length +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:77 +msgid "{0, plural, one {# municipality with reviewed financial data} other {# municipalities with reviewed financial data}}" +msgstr "{0, plural, one {# municipality with reviewed financial data} other {# municipalities with reviewed financial data}}" + +#. placeholder {0}: municipality.available_years.length +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:105 +msgid "{0, plural, one {# year} other {# years}}" +msgstr "{0, plural, one {# year} other {# years}}" + #. placeholder {0}: department.agenciesHeading #. placeholder {0}: department.leadershipHeading #. placeholder {0}: department.name @@ -32,6 +42,11 @@ msgstr "— Canada Spends Team" msgid "{0}" msgstr "{0}" +#. placeholder {0}: verification.summary.fail +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:250 +msgid "{0} failed" +msgstr "{0} failed" + #. placeholder {0}: department.name #: src/app/[lang]/(main)/federal/spending/[year]/[department]/page.tsx:451 msgid "{0} figures are net expenditures by standard object from Public Accounts Volume II, and will not match the Volume I consolidated headline totals. See the <0>methodology for details." @@ -83,6 +98,23 @@ msgstr "{0} line items · all figures in real {baseYear} dollars" msgid "{0} of federal income tax" msgstr "{0} of federal income tax" +#. placeholder {0}: verification.summary.pass +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:241 +msgid "{0} passed" +msgstr "{0} passed" + +#. placeholder {0}: formatCurrency( statement.per_capita.total_expenses, lang, ) +#. placeholder {0}: formatCurrency( statement.per_capita.total_revenue, lang, ) +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:503 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:528 +msgid "{0} per resident" +msgstr "{0} per resident" + +#. placeholder {0}: verification.summary.skip +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:245 +msgid "{0} skipped" +msgstr "{0} skipped" + #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear #: src/components/JurisdictionPageContent.tsx:357 @@ -99,6 +131,14 @@ msgstr "{0}’s share of federal spending" msgid "{0}{provinceSuffix} - No financial data is currently available under the First Nations Financial Transparency Act." msgstr "{0}{provinceSuffix} - No financial data is currently available under the First Nations Financial Transparency Act." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:568 +msgid "{censusYear} Census" +msgstr "{censusYear} Census" + +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:102 +msgid "{latestYear} financial statements" +msgstr "{latestYear} financial statements" + #: src/app/[lang]/(main)/federal/spending/[year]/[department]/page.tsx:63 msgid "{name} Spending, FY {fy} | Canada Spends" msgstr "{name} Spending, FY {fy} | Canada Spends" @@ -141,8 +181,8 @@ msgstr "© 2025 Canada Spends. All rights reserved. A Project of <0>Build Canada msgid "100,000" msgstr "100,000" -#: src/components/MainLayout/_components/DesktopNav.tsx:279 -#: src/components/MainLayout/_components/MobileMenu.tsx:188 +#: src/components/MainLayout/_components/DesktopNav.tsx:287 +#: src/components/MainLayout/_components/MobileMenu.tsx:197 msgid "About" msgstr "About" @@ -150,7 +190,7 @@ msgstr "About" msgid "About GlobaLeaks" msgstr "About GlobaLeaks" -#: src/components/MainLayout/_components/DesktopNav.tsx:293 +#: src/components/MainLayout/_components/DesktopNav.tsx:301 #: src/components/MainLayout/Footer.tsx:41 msgid "About Us" msgstr "About Us" @@ -168,6 +208,10 @@ msgstr "Accounting and consolidation adjustments" msgid "Accounts payable, long-term debt, and other obligations owed to external parties." msgstr "Accounts payable, long-term debt, and other obligations owed to external parties." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:39 +msgid "Accumulated surplus" +msgstr "Accumulated surplus" + #: src/components/first-nations/FinancialPositionStats.tsx:310 msgid "Accumulated Surplus" msgstr "Accumulated Surplus" @@ -196,6 +240,11 @@ msgstr "All data presented regarding government spending is sourced directly fro msgid "All expenses incurred during the fiscal year including program delivery, administration, and capital costs." msgstr "All expenses incurred during the fiscal year including program delivery, administration, and capital costs." +#: src/components/MainLayout/_components/DesktopNav.tsx:148 +#: src/components/MainLayout/_components/MobileMenu.tsx:132 +msgid "All financial statements" +msgstr "All financial statements" + #: src/app/[lang]/(main)/budget/page.tsx:270 #: src/app/[lang]/(main)/federal/budget/page.tsx:309 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." @@ -213,6 +262,18 @@ msgstr "All of the information we use comes from publicly available databases an msgid "All program and operating spending recorded for the fiscal year." msgstr "All program and operating spending recorded for the fiscal year." +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:65 +msgid "All provinces and territories" +msgstr "All provinces and territories" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:536 +msgid "All reported municipal expenses" +msgstr "All reported municipal expenses" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:511 +msgid "All reported municipal revenue" +msgstr "All reported municipal revenue" + #: src/components/JurisdictionPageContent.tsx:163 msgid "All revenue collected during the fiscal year, including taxes, transfers, and other sources." msgstr "All revenue collected during the fiscal year, including taxes, transfers, and other sources." @@ -257,6 +318,11 @@ msgstr "Annual interest payments on outstanding debt. This represents the cost o msgid "Annual municipal spending per {0} resident" msgstr "Annual municipal spending per {0} resident" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:47 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:486 +msgid "Annual surplus (deficit)" +msgstr "Annual surplus (deficit)" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:94 msgid "Application ID" msgstr "Application ID" @@ -295,6 +361,10 @@ msgstr "As of fiscal year end {0}" msgid "As of fiscal year end {fiscalYear}" msgstr "As of fiscal year end {fiscalYear}" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:607 +msgid "Assets, liabilities, and accumulated surplus at the end of the fiscal year." +msgstr "Assets, liabilities, and accumulated surplus at the end of the fiscal year." + #: src/components/first-nations/FirstNationsPageContent.tsx:373 msgid "Assets, liabilities, and net financial position as of the end of fiscal year {fiscalYear}." msgstr "Assets, liabilities, and net financial position as of the end of fiscal year {fiscalYear}." @@ -335,6 +405,10 @@ msgstr "Based on income of {0}" msgid "BPA credit ({0} × {1})" msgstr "BPA credit ({0} × {1})" +#: src/components/HeroButtons.tsx:87 +msgid "Browse all financial statements" +msgstr "Browse all financial statements" + #: src/app/[lang]/(main)/first-nations/[bcid]/page.tsx:133 msgid "Browse all First Nations" msgstr "Browse all First Nations" @@ -428,6 +502,10 @@ msgstr "Cash, investments, accounts receivable, and other assets that can be con msgid "Category" msgstr "Category" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:558 +msgid "Census context makes municipal totals easier to compare, while recognizing that municipal boundaries and service responsibilities differ." +msgstr "Census context makes municipal totals easier to compare, while recognizing that municipal boundaries and service responsibilities differ." + #: src/components/first-nations/RemunerationOverview.tsx:401 msgid "Chief" msgstr "Chief" @@ -448,6 +526,10 @@ msgstr "Co-Applicant(s)" msgid "Collaboration Type" msgstr "Collaboration Type" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:555 +msgid "Community context" +msgstr "Community context" + #: src/app/[lang]/(main)/first-nations/remuneration/page.tsx:34 msgid "Compare remuneration data across First Nations from annual reports published under the FNFTA." msgstr "Compare remuneration data across First Nations from annual reports published under the FNFTA." @@ -494,8 +576,8 @@ msgstr "consolidated federal revenue (Vol I)" msgid "consolidated federal spending (Vol I)" msgstr "consolidated federal spending (Vol I)" -#: src/components/MainLayout/_components/DesktopNav.tsx:310 -#: src/components/MainLayout/_components/MobileMenu.tsx:195 +#: src/components/MainLayout/_components/DesktopNav.tsx:318 +#: src/components/MainLayout/_components/MobileMenu.tsx:204 #: src/components/MainLayout/Footer.tsx:54 msgid "Contact" msgstr "Contact" @@ -574,6 +656,10 @@ msgstr "Department Spending Reductions" msgid "Description" msgstr "Description" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:277 +msgid "Details" +msgstr "Details" + #: src/components/Sankey/BudgetSankey.tsx:433 msgid "Direct program expenses" msgstr "Direct program expenses" @@ -716,9 +802,15 @@ msgstr "Expected Results" #: src/components/first-nations/RemunerationOverview.tsx:651 #: src/components/first-nations/RemunerationOverview.tsx:797 #: src/components/first-nations/RemunerationTable.tsx:259 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:183 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:431 msgid "Expenses" msgstr "Expenses" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:589 +msgid "Expenses per resident" +msgstr "Expenses per resident" + #: 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." @@ -739,7 +831,7 @@ msgstr "Explore financial data from First Nations across Canada. Data is extract msgid "Explore financial data from First Nations across Canada. View statements of operations, financial positions, and remuneration from annual reports published under the First Nations Financial Transparency Act (FNFTA)." msgstr "Explore financial data from First Nations across Canada. View statements of operations, financial positions, and remuneration from annual reports published under the First Nations Financial Transparency Act (FNFTA)." -#: src/components/HeroButtons.tsx:110 +#: src/components/HeroButtons.tsx:118 msgid "Explore First Nations Spending" msgstr "Explore First Nations Spending" @@ -763,6 +855,14 @@ msgstr "Explore other federal departments" msgid "Explore Provincial Spending" msgstr "Explore Provincial Spending" +#: src/app/[lang]/(main)/municipal/page.tsx:44 +msgid "Explore revenue, expenses, assets, liabilities, and accumulated surplus reported by municipalities across Canada." +msgstr "Explore revenue, expenses, assets, liabilities, and accumulated surplus reported by municipalities across Canada." + +#: src/app/[lang]/(main)/municipal/page.tsx:22 +msgid "Explore reviewed financial statement data for municipalities across Canada." +msgstr "Explore reviewed financial statement data for municipalities across Canada." + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:137 msgid "External ID" msgstr "External ID" @@ -771,6 +871,10 @@ msgstr "External ID" msgid "Facts about Spending" msgstr "Facts about Spending" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:293 +msgid "Fail" +msgstr "Fail" + #: src/app/[lang]/(main)/articles/page.tsx:55 msgid "Featured Articles" msgstr "Featured Articles" @@ -836,6 +940,10 @@ msgstr "Federal Total" msgid "Federal workforce" msgstr "Federal workforce" +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:57 +msgid "Filter by province or territory" +msgstr "Filter by province or territory" + #: src/components/DepartmentList.tsx:15 msgid "Finance Canada" msgstr "Finance Canada" @@ -868,15 +976,29 @@ msgstr "Financial liabilities less financial assets" msgid "Financial liabilities minus financial assets. A negative value indicates net financial assets." msgstr "Financial liabilities minus financial assets. A negative value indicates net financial assets." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:604 +msgid "Financial position" +msgstr "Financial position" + #. placeholder {0}: jurisdiction.financialYear #: src/components/JurisdictionPageContent.tsx:399 msgid "Financial Position {0}" msgstr "Financial Position {0}" +#. placeholder {0}: statement.fiscal_year +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:475 +msgid "Financial summary for {0}" +msgstr "Financial summary for {0}" + #: src/components/first-nations/FirstNationsPageContent.tsx:343 msgid "Financial Summary FY {fiscalYear}" msgstr "Financial Summary FY {fiscalYear}" +#. placeholder {0}: statement.fiscal_year +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:594 +msgid "Financial year {0}" +msgstr "Financial year {0}" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:398 #: src/app/[lang]/(main)/tax-visualizer/page.tsx:635 msgid "First" @@ -888,8 +1010,8 @@ msgid "First Nation" msgstr "First Nation" #: src/app/[lang]/(main)/first-nations/[bcid]/page.tsx:59 -#: src/components/MainLayout/_components/DesktopNav.tsx:188 -#: src/components/MainLayout/_components/MobileMenu.tsx:147 +#: src/components/MainLayout/_components/DesktopNav.tsx:196 +#: src/components/MainLayout/_components/MobileMenu.tsx:156 msgid "First Nations" msgstr "First Nations" @@ -951,6 +1073,10 @@ msgstr "FY" msgid "FY {0} Revenue and Spending" msgstr "FY {0} Revenue and Spending" +#: src/components/FinancialYearSelector.tsx:38 +msgid "FY {year}" +msgstr "FY {year}" + #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.name #: src/components/JurisdictionPageContent.tsx:348 @@ -961,7 +1087,7 @@ msgstr "Get data-driven insights into how the {0} government’s revenue and spe msgid "Get data-driven insights into how the federal government’s revenue and spending affect Canadian lives and programs." msgstr "Get data-driven insights into how the federal government’s revenue and spending affect Canadian lives and programs." -#: src/components/MainLayout/_components/DesktopNav.tsx:302 +#: src/components/MainLayout/_components/DesktopNav.tsx:310 #: src/components/MainLayout/Footer.tsx:49 msgid "Get Involved" msgstr "Get Involved" @@ -1094,6 +1220,10 @@ msgstr "How federal spending pages are built from Public Accounts of Canada data msgid "How ministry and Sankey totals are allocated" msgstr "How ministry and Sankey totals are allocated" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:478 +msgid "How much the municipality reported earning and spending during the fiscal year." +msgstr "How much the municipality reported earning and spending during the fiscal year." + #: src/app/[lang]/(main)/federal/spending/[year]/page.tsx:66 msgid "How the Government of Canada spent its budget in fiscal year {fy}, by department, theme, and program." msgstr "How the Government of Canada spent its budget in fiscal year {fy}, by department, theme, and program." @@ -1145,6 +1275,10 @@ msgstr "In-depth analysis and insights about Canadian government spending, budge msgid "Income Tax Revenues" msgstr "Income Tax Revenues" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:228 +msgid "Independent verification completed" +msgstr "Independent verification completed" + #: src/components/DepartmentList.tsx:21 msgid "Indigenous Services Canada + Crown-Indigenous Relations and Northern Affairs Canada" msgstr "Indigenous Services Canada + Crown-Indigenous Relations and Northern Affairs Canada" @@ -1190,9 +1324,9 @@ msgstr "It doesn't have to be this way." msgid "Item" msgstr "Item" -#: src/components/MainLayout/_components/DesktopNav.tsx:330 +#: src/components/MainLayout/_components/DesktopNav.tsx:338 #: src/components/MainLayout/_components/MobileMenu.tsx:22 -#: src/components/MainLayout/_components/MobileMenu.tsx:211 +#: src/components/MainLayout/_components/MobileMenu.tsx:220 msgid "Join Build Canada" msgstr "Join Build Canada" @@ -1204,6 +1338,10 @@ msgstr "Key Dates" msgid "Keywords" msgstr "Keywords" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:572 +msgid "Land area" +msgstr "Land area" + #: src/components/first-nations/FirstNationsPageContent.tsx:395 msgid "Land Claims" msgstr "Land Claims" @@ -1290,6 +1428,18 @@ msgstr "More coming soon..." msgid "Municipal" msgstr "Municipal" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:574 +msgid "Municipal census subdivision" +msgstr "Municipal census subdivision" + +#: src/app/[lang]/(main)/municipal/page.tsx:41 +msgid "Municipal Financial Statements" +msgstr "Municipal Financial Statements" + +#: src/app/[lang]/(main)/municipal/page.tsx:21 +msgid "Municipal Financial Statements | Canada Spends" +msgstr "Municipal Financial Statements | Canada Spends" + #: src/components/first-nations/RemunerationOverview.tsx:788 #: src/components/first-nations/RemunerationTable.tsx:114 msgid "Name" @@ -1324,6 +1474,10 @@ msgstr "Net Debt" msgid "Net Debt is what remains after subtracting financial assets (like cash and investments) from the Total Debt. It represents the debt that isn't immediately covered by liquid assets." msgstr "Net Debt is what remains after subtracting financial assets (like cash and investments) from the Total Debt. It represents the debt that isn't immediately covered by liquid assets." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:35 +msgid "Net financial assets (debt)" +msgstr "Net financial assets (debt)" + #: src/components/Sankey/BudgetSankey.tsx:517 msgid "Net Foreign Exchange Revenue and Return on Investments" msgstr "Net Foreign Exchange Revenue and Return on Investments" @@ -1372,6 +1526,10 @@ msgstr "No individual entries available." msgid "No matching line items." msgstr "No matching line items." +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:121 +msgid "No municipalities match those filters." +msgstr "No municipalities match those filters." + #: src/components/first-nations/FirstNationsNotes.tsx:20 msgid "No notes available." msgstr "No notes available." @@ -1464,6 +1622,10 @@ msgstr "On-Reserve Pop." msgid "Open Tor Browser and wait for it to connect." msgstr "Open Tor Browser and wait for it to connect." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:41 +msgid "Opening accumulated surplus" +msgstr "Opening accumulated surplus" + #: src/app/[lang]/(main)/budget/page.tsx:195 #: src/app/[lang]/(main)/federal/budget/page.tsx:234 msgid "Operational Spend" @@ -1515,15 +1677,26 @@ msgstr "Other transfer payments" msgid "Our government is going to have to make hard choices about our nation's spending to ensure we can invest in creating a competitive, resilient, and independent nation. We care about giving Canadians the facts about spending so they can engage in this conversation with elected officials." msgstr "Our government is going to have to make hard choices about our nation's spending to ensure we can invest in creating a competitive, resilient, and independent nation. We care about giving Canadians the facts about spending so they can engage in this conversation with elected officials." -#: src/components/MainLayout/_components/DesktopNav.tsx:202 -#: src/components/MainLayout/_components/MobileMenu.tsx:155 +#: src/components/MainLayout/_components/DesktopNav.tsx:210 +#: src/components/MainLayout/_components/MobileMenu.tsx:164 msgid "Overview" msgstr "Overview" +#. placeholder {0}: fact.source_page +#. placeholder {0}: item.source_page +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:147 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:453 +msgid "page {0}" +msgstr "page {0}" + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:225 msgid "Parenthesized negatives, internal consolidation adjustments, and total or subtotal rows are flagged and excluded from Sankey leaves to avoid double counting, but they are retained in the line-item tables so the full detail remains available." msgstr "Parenthesized negatives, internal consolidation adjustments, and total or subtotal rows are flagged and excluded from Sankey leaves to avoid double counting, but they are retained in the line-item tables so the full detail remains available." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:289 +msgid "Pass" +msgstr "Pass" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:338 msgid "Payment Amount" msgstr "Payment Amount" @@ -1567,6 +1740,14 @@ msgstr "Pollution pricing" msgid "Pollution pricing proceeds" msgstr "Pollution pricing proceeds" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:566 +msgid "Population" +msgstr "Population" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:579 +msgid "Population density" +msgstr "Population density" + #: src/components/first-nations/RemunerationOverview.tsx:791 #: src/components/first-nations/RemunerationTable.tsx:107 msgid "Position" @@ -1598,6 +1779,10 @@ msgstr "Program Name" msgid "Program Type" msgstr "Program Type" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:540 +msgid "Program, service, administration, financing, and amortization expenses." +msgstr "Program, service, administration, financing, and amortization expenses." + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:235 msgid "Project Number" msgstr "Project Number" @@ -1708,8 +1893,8 @@ msgid "Regions" msgstr "Regions" #: src/components/first-nations/RemunerationTable.tsx:233 -#: src/components/MainLayout/_components/DesktopNav.tsx:210 -#: src/components/MainLayout/_components/MobileMenu.tsx:166 +#: src/components/MainLayout/_components/DesktopNav.tsx:218 +#: src/components/MainLayout/_components/MobileMenu.tsx:175 msgid "Remuneration" msgstr "Remuneration" @@ -1717,6 +1902,15 @@ msgstr "Remuneration" msgid "Remuneration and Expenses" msgstr "Remuneration and Expenses" +#. placeholder {0}: fact.raw_label +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:137 +msgid "Reported as “{0}”" +msgstr "Reported as “{0}”" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:409 +msgid "Reported revenue and expense details" +msgstr "Reported revenue and expense details" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:277 msgid "Reporting Organization" msgstr "Reporting Organization" @@ -1726,6 +1920,14 @@ msgstr "Reporting Organization" msgid "Research Subject" msgstr "Research Subject" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:584 +msgid "Residents per km²" +msgstr "Residents per km²" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:271 +msgid "Result" +msgstr "Result" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:267 msgid "Results Achieved" msgstr "Results Achieved" @@ -1736,6 +1938,8 @@ msgstr "Retirement Benefits" #: src/app/[lang]/(main)/budget/page.tsx:184 #: src/app/[lang]/(main)/federal/budget/page.tsx:223 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:177 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:429 #: src/components/Sankey/BudgetSankey.tsx:456 msgid "Revenue" msgstr "Revenue" @@ -1748,6 +1952,25 @@ msgstr "Revenue and Expenses FY {fiscalYear}" msgid "revenue minus spending (Vol I)" msgstr "revenue minus spending (Vol I)" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:488 +msgid "Revenue remaining after expenses" +msgstr "Revenue remaining after expenses" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:383 +msgid "Revenue sources flow in from the left and spending by service or function flows out on the right. Revenue offsets and losses are shown as outflows, while expense recoveries are shown as inflows. Values come from detailed schedules in the audited financial statements." +msgstr "Revenue sources flow in from the left and spending by service or function flows out on the right. Revenue offsets and losses are shown as outflows, while expense recoveries are shown as inflows. Values come from detailed schedules in the audited financial statements." + +#. placeholder {0}: verification.reviewed_by +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:234 +msgid "Reviewed by {0} on {reviewedAt}" +msgstr "Reviewed by {0} on {reviewedAt}" + +#. placeholder {0}: municipality.name +#. placeholder {1}: municipality.province_name +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:351 +msgid "Reviewed financial statement data for {0} in {1}, for the fiscal year ending {fiscalYearEnd}." +msgstr "Reviewed financial statement data for {0} in {1}, for the fiscal year ending {fiscalYearEnd}." + #: src/components/FederalWorkforceStrip.tsx:89 msgid "salaries and benefits (standard object)" msgstr "salaries and benefits (standard object)" @@ -1768,6 +1991,11 @@ msgstr "Save your receipt code securely—you will need it to check for response msgid "Search line items…" msgstr "Search line items…" +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:45 +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:51 +msgid "Search municipalities" +msgstr "Search municipalities" + #: src/app/[lang]/(main)/whistleblowers/page.tsx:153 msgid "Security Tips" msgstr "Security Tips" @@ -1809,6 +2037,10 @@ msgstr "Showing {0} of {1} First Nations" msgid "Showing {0} of {1} results" msgstr "Showing {0} of {1} results" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:291 +msgid "Skipped" +msgstr "Skipped" + #: src/components/Sankey/BudgetSankey.tsx:347 msgid "Social Security" msgstr "Social Security" @@ -1827,6 +2059,10 @@ msgstr "Source" msgid "Source and licence" msgstr "Source and licence" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:641 +msgid "Source and methodology" +msgstr "Source and methodology" + #: src/components/first-nations/RemunerationOverview.tsx:601 msgid "Source PDF" msgstr "Source PDF" @@ -1879,9 +2115,9 @@ msgstr "Spending by ministry, FY {0}" msgid "Spending data is not yet available for this province and year combination. The income tax calculation above is still accurate based on {year} tax rates." msgstr "Spending data is not yet available for this province and year combination. The income tax calculation above is still accurate based on {year} tax rates." -#: src/components/MainLayout/_components/DesktopNav.tsx:249 -#: src/components/MainLayout/_components/DesktopNav.tsx:268 -#: src/components/MainLayout/_components/MobileMenu.tsx:181 +#: src/components/MainLayout/_components/DesktopNav.tsx:257 +#: src/components/MainLayout/_components/DesktopNav.tsx:276 +#: src/components/MainLayout/_components/MobileMenu.tsx:190 msgid "Spending Database" msgstr "Spending Database" @@ -1986,16 +2222,24 @@ msgstr "Tax Visualizer | Canada Spends" msgid "Tax Year" msgstr "Tax Year" -#: src/components/MainLayout/_components/DesktopNav.tsx:241 -#: src/components/MainLayout/_components/DesktopNav.tsx:261 -#: src/components/MainLayout/_components/MobileMenu.tsx:174 +#: src/components/MainLayout/_components/DesktopNav.tsx:249 +#: src/components/MainLayout/_components/DesktopNav.tsx:269 +#: src/components/MainLayout/_components/MobileMenu.tsx:183 msgid "Taxes" msgstr "Taxes" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:515 +msgid "Taxes, transfers, fees, services, and other reported income." +msgstr "Taxes, transfers, fees, services, and other reported income." + #: src/components/Sankey/BudgetSankey.tsx:395 msgid "Territorial Formula Financing" msgstr "Territorial Formula Financing" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:274 +msgid "Test" +msgstr "Test" + #: src/app/[lang]/(main)/about/faq.tsx:61 msgid "The best way to support us is to engage with our content. Like, share, and comment on our posts on social media. Even better, share our work with a friend and encourage them to do the same. Together, we can expand the conversation and reach more people with these important facts." msgstr "The best way to support us is to engage with our content. Like, share, and comment on our posts on social media. Even better, share our work with a friend and encourage them to do the same. Together, we can expand the conversation and reach more people with these important facts." @@ -2012,10 +2256,20 @@ msgstr "The difference between revenue and spending. A surplus indicates revenue msgid "The difference between total revenue and total expenses. A surplus indicates revenue exceeded expenses." msgstr "The difference between total revenue and total expenses. A surplus indicates revenue exceeded expenses." +#. placeholder {0}: formatNumber(context.population, lang, 0) +#. placeholder {1}: formatNumber(context.area_sq_km || 0, lang) +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:359 +msgid "The municipality serves a {censusYear} Census population of {0} across {1} square kilometres." +msgstr "The municipality serves a {censusYear} Census population of {0} across {1} square kilometres." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:194 msgid "The previous hand-authored pages contained curated aggregations — for example, hand-massaged negative leaves and thematic groupings that split a single ministry across categories. Every node where the generated tree differs from the previous values is enumerated in the parity report with a reason code (basis, mapping, rounding, or source correction)." msgstr "The previous hand-authored pages contained curated aggregations — for example, hand-massaged negative leaves and thematic groupings that split a single ministry across categories. Every node where the generated tree differs from the previous values is enumerated in the parity report with a reason code (basis, mapping, rounding, or source correction)." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:490 +msgid "The reported difference between annual revenue and expenses, including any separately presented adjustments." +msgstr "The reported difference between annual revenue and expenses, including any separately presented adjustments." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:253 msgid "The Sankey uses a curated thematic tree. A maintained mapping assigns each Public Accounts line to a thematic node, mostly at the ministry or organization level with line-level overrides where needed. Each node is truncated to its largest children with the remainder rolled into an “Other” aggregate." msgstr "The Sankey uses a curated thematic tree. A maintained mapping assigns each Public Accounts line to a thematic node, mostly at the ministry or organization level with line-level overrides where needed. Each node is truncated to its largest children with the remainder rolled into an “Other” aggregate." @@ -2036,6 +2290,10 @@ msgstr "Thematic mapping" msgid "Theme" msgstr "Theme" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:412 +msgid "These are the detailed rows extracted from the audited statement. This table preserves every signed amount, including negative and consolidation entries that the Sankey repositions as offsets or recoveries." +msgstr "These are the detailed rows extracted from the audited statement. This table preserves every signed amount, including negative and consolidation entries that the Sankey repositions as offsets or recoveries." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:57 msgid "These pages are generated from the Public Accounts of Canada. This page documents the accounting bases, curation choices, and known differences so the numbers are traceable to source." msgstr "These pages are generated from the Public Accounts of Canada. This page documents the accounting bases, curation choices, and known differences so the numbers are traceable to source." @@ -2061,7 +2319,7 @@ msgstr "This visualization shows how your income tax contributions are allocated msgid "to help us support our mission." msgstr "to help us support our mission." -#: src/components/MainLayout/_components/DesktopNav.tsx:227 +#: src/components/MainLayout/_components/DesktopNav.tsx:235 msgid "Tools" msgstr "Tools" @@ -2105,6 +2363,11 @@ msgstr "Total Debt is the government's complete outstanding debt. This is the fi msgid "Total difference" msgstr "Total difference" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:45 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:524 +msgid "Total expenses" +msgstr "Total expenses" + #: src/components/first-nations/FirstNationsPageContent.tsx:261 msgid "Total Expenses" msgstr "Total Expenses" @@ -2117,10 +2380,18 @@ msgstr "Total expenses in FY {fiscalYear}" msgid "Total Federal Tax" msgstr "Total Federal Tax" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:31 +msgid "Total financial assets" +msgstr "Total financial assets" + #: src/components/first-nations/FinancialPositionStats.tsx:168 msgid "Total Financial Assets" msgstr "Total Financial Assets" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:33 +msgid "Total liabilities" +msgstr "Total liabilities" + #: src/components/first-nations/FinancialPositionStats.tsx:191 msgid "Total Liabilities" msgstr "Total Liabilities" @@ -2129,6 +2400,10 @@ msgstr "Total Liabilities" msgid "Total net assets" msgstr "Total net assets" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:37 +msgid "Total non-financial assets" +msgstr "Total non-financial assets" + #: src/components/first-nations/ClaimsTable.tsx:148 msgid "Total Payments" msgstr "Total Payments" @@ -2147,6 +2422,11 @@ msgstr "Total property tax revenue divided by population. Primary revenue source msgid "Total Remuneration" msgstr "Total Remuneration" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:43 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:499 +msgid "Total revenue" +msgstr "Total revenue" + #: src/components/first-nations/FirstNationsPageContent.tsx:241 #: src/components/JurisdictionPageContent.tsx:160 msgid "Total Revenue" @@ -2241,6 +2521,14 @@ msgstr "Units" msgid "Use a computer you trust, not one provided by your employer." msgstr "Use a computer you trust, not one provided by your employer." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:644 +msgid "Values are extracted from the municipality’s published financial statements and independently reviewed before publication. Page links point to the reported figure in the source document." +msgstr "Values are extracted from the municipality’s published financial statements and independently reviewed before publication. Page links point to the reported figure in the source document." + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:230 +msgid "Verification requires attention" +msgstr "Verification requires attention" + #: src/components/DepartmentList.tsx:45 msgid "Veterans Affairs" msgstr "Veterans Affairs" @@ -2250,6 +2538,11 @@ msgstr "Veterans Affairs" msgid "View {year} financial statements for {0}{provinceSuffix}. Includes statement of operations, financial position, and remuneration data from the First Nations Financial Transparency Act annual report." msgstr "View {year} financial statements for {0}{provinceSuffix}. Includes statement of operations, financial position, and remuneration data from the First Nations Financial Transparency Act annual report." +#. placeholder {0}: verification.summary.total +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:262 +msgid "View all {0} verification checks" +msgstr "View all {0} verification checks" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:858 #: src/components/TaxBreakdownAccordion.tsx:100 msgid "View detailed tax breakdown" @@ -2264,7 +2557,7 @@ msgstr "View Federal 2025 Budget Details" msgid "View financial data for {0}{provinceSuffix} from the First Nations Financial Transparency Act annual reports." msgstr "View financial data for {0}{provinceSuffix} from the First Nations Financial Transparency Act annual reports." -#: src/components/first-nations/FirstNationsYearSelector.tsx:26 +#: src/components/FinancialYearSelector.tsx:22 msgid "View Other Fiscal Years" msgstr "View Other Fiscal Years" @@ -2272,6 +2565,10 @@ msgstr "View Other Fiscal Years" msgid "View source data" msgstr "View source data" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:658 +msgid "View source financial statements" +msgstr "View source financial statements" + #: src/app/[lang]/(main)/budget/page.tsx:242 #: src/app/[lang]/(main)/federal/budget/page.tsx:281 #: src/app/[lang]/(main)/federal/spending/[year]/page.tsx:231 @@ -2372,14 +2669,18 @@ msgstr "What Makes a Good Tip?" msgid "Where do you get the data on your website?" msgstr "Where do you get the data on your website?" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:373 +msgid "Where the money came from and where it went" +msgstr "Where the money came from and where it went" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:826 #: src/app/[lang]/(main)/tax-visualizer/page.tsx:880 msgid "Where Your Tax Dollars Go" msgstr "Where Your Tax Dollars Go" #: src/app/[lang]/(main)/whistleblowers/page.tsx:44 -#: src/components/MainLayout/_components/DesktopNav.tsx:318 -#: src/components/MainLayout/_components/MobileMenu.tsx:202 +#: src/components/MainLayout/_components/DesktopNav.tsx:326 +#: src/components/MainLayout/_components/MobileMenu.tsx:211 #: src/components/MainLayout/Footer.tsx:59 msgid "Whistleblowers" msgstr "Whistleblowers" diff --git a/src/locales/fr.po b/src/locales/fr.po index 1e5fbe05..f2130638 100644 --- a/src/locales/fr.po +++ b/src/locales/fr.po @@ -17,6 +17,16 @@ msgstr "" msgid "— Canada Spends Team" msgstr "— L'équipe de Canada Spends" +#. placeholder {0}: filtered.length +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:77 +msgid "{0, plural, one {# municipality with reviewed financial data} other {# municipalities with reviewed financial data}}" +msgstr "{0, plural, one {# municipalité avec des données financières révisées} other {# municipalités avec des données financières révisées}}" + +#. placeholder {0}: municipality.available_years.length +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:105 +msgid "{0, plural, one {# year} other {# years}}" +msgstr "{0, plural, one {# année} other {# années}}" + #. placeholder {0}: department.agenciesHeading #. placeholder {0}: department.leadershipHeading #. placeholder {0}: department.name @@ -32,6 +42,11 @@ msgstr "— L'équipe de Canada Spends" msgid "{0}" msgstr "{0}" +#. placeholder {0}: verification.summary.fail +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:250 +msgid "{0} failed" +msgstr "{0} échoués" + #. placeholder {0}: department.name #: src/app/[lang]/(main)/federal/spending/[year]/[department]/page.tsx:451 msgid "{0} figures are net expenditures by standard object from Public Accounts Volume II, and will not match the Volume I consolidated headline totals. See the <0>methodology for details." @@ -83,6 +98,23 @@ msgstr "{0} postes · tous les montants sont en dollars réels de {baseYear}" msgid "{0} of federal income tax" msgstr "{0} de l'impôt fédéral sur le revenu" +#. placeholder {0}: verification.summary.pass +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:241 +msgid "{0} passed" +msgstr "{0} réussis" + +#. placeholder {0}: formatCurrency( statement.per_capita.total_expenses, lang, ) +#. placeholder {0}: formatCurrency( statement.per_capita.total_revenue, lang, ) +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:503 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:528 +msgid "{0} per resident" +msgstr "{0} par habitant" + +#. placeholder {0}: verification.summary.skip +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:245 +msgid "{0} skipped" +msgstr "{0} ignorés" + #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.financialYear #: src/components/JurisdictionPageContent.tsx:357 @@ -99,6 +131,14 @@ msgstr "Part de {0} dans les dépenses fédérales" msgid "{0}{provinceSuffix} - No financial data is currently available under the First Nations Financial Transparency Act." msgstr "{0}{provinceSuffix} - Aucune donnée financière n'est actuellement disponible en vertu de la Loi sur la transparence financière des Premières Nations." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:568 +msgid "{censusYear} Census" +msgstr "Recensement de {censusYear}" + +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:102 +msgid "{latestYear} financial statements" +msgstr "États financiers de {latestYear}" + #: src/app/[lang]/(main)/federal/spending/[year]/[department]/page.tsx:63 msgid "{name} Spending, FY {fy} | Canada Spends" msgstr "Dépenses de {name}, exercice {fy} | Canada Spends" @@ -141,8 +181,8 @@ msgstr "© 2025 Canada Spends. Tous droits réservés. Un projet de <0>Build Can msgid "100,000" msgstr "100 000" -#: src/components/MainLayout/_components/DesktopNav.tsx:279 -#: src/components/MainLayout/_components/MobileMenu.tsx:188 +#: src/components/MainLayout/_components/DesktopNav.tsx:287 +#: src/components/MainLayout/_components/MobileMenu.tsx:197 msgid "About" msgstr "À propos" @@ -150,7 +190,7 @@ msgstr "À propos" msgid "About GlobaLeaks" msgstr "À propos de GlobaLeaks" -#: src/components/MainLayout/_components/DesktopNav.tsx:293 +#: src/components/MainLayout/_components/DesktopNav.tsx:301 #: src/components/MainLayout/Footer.tsx:41 msgid "About Us" msgstr "À propos de nous" @@ -168,6 +208,10 @@ msgstr "Ajustements comptables et de consolidation" msgid "Accounts payable, long-term debt, and other obligations owed to external parties." msgstr "Comptes créditeurs, dette à long terme et autres obligations envers des tiers." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:39 +msgid "Accumulated surplus" +msgstr "Excédent accumulé" + #: src/components/first-nations/FinancialPositionStats.tsx:310 msgid "Accumulated Surplus" msgstr "Excédent accumulé" @@ -196,6 +240,11 @@ msgstr "Toutes les données présentées concernant les dépenses gouvernemental msgid "All expenses incurred during the fiscal year including program delivery, administration, and capital costs." msgstr "Toutes les dépenses engagées au cours de l'exercice financier, y compris la prestation de programmes, l'administration et les coûts d'immobilisations." +#: src/components/MainLayout/_components/DesktopNav.tsx:148 +#: src/components/MainLayout/_components/MobileMenu.tsx:132 +msgid "All financial statements" +msgstr "Tous les états financiers" + #: src/app/[lang]/(main)/budget/page.tsx:270 #: src/app/[lang]/(main)/federal/budget/page.tsx:309 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." @@ -213,6 +262,18 @@ msgstr "Toutes les informations que nous utilisons proviennent des bases de donn msgid "All program and operating spending recorded for the fiscal year." msgstr "Toutes les dépenses de programmes et d'exploitation enregistrées pour l'exercice financier." +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:65 +msgid "All provinces and territories" +msgstr "Toutes les provinces et tous les territoires" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:536 +msgid "All reported municipal expenses" +msgstr "Toutes les charges municipales déclarées" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:511 +msgid "All reported municipal revenue" +msgstr "Tous les revenus municipaux déclarés" + #: src/components/JurisdictionPageContent.tsx:163 msgid "All revenue collected during the fiscal year, including taxes, transfers, and other sources." msgstr "Tous les revenus collectés pendant l'exercice financier, y compris les impôts, les transferts et d'autres sources." @@ -257,6 +318,11 @@ msgstr "Paiements d'intérêts annuels sur la dette en cours. Cela représente l msgid "Annual municipal spending per {0} resident" msgstr "Dépenses municipales annuelles par résident de {0}" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:47 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:486 +msgid "Annual surplus (deficit)" +msgstr "Excédent (déficit) annuel" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:94 msgid "Application ID" msgstr "ID de la demande" @@ -295,6 +361,10 @@ msgstr "À la fin de l'exercice financier {0}" msgid "As of fiscal year end {fiscalYear}" msgstr "À la fin de l'exercice financier {fiscalYear}" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:607 +msgid "Assets, liabilities, and accumulated surplus at the end of the fiscal year." +msgstr "Actifs, passifs et excédent accumulé à la fin de l’exercice financier." + #: src/components/first-nations/FirstNationsPageContent.tsx:373 msgid "Assets, liabilities, and net financial position as of the end of fiscal year {fiscalYear}." msgstr "Actifs, passifs et situation financière nette à la fin de l'exercice financier {fiscalYear}." @@ -335,6 +405,10 @@ msgstr "Basé sur un revenu de {0}" msgid "BPA credit ({0} × {1})" msgstr "Crédit MPC ({0} × {1})" +#: src/components/HeroButtons.tsx:87 +msgid "Browse all financial statements" +msgstr "Parcourir tous les états financiers" + #: src/app/[lang]/(main)/first-nations/[bcid]/page.tsx:133 msgid "Browse all First Nations" msgstr "Parcourir toutes les Premières Nations" @@ -428,6 +502,10 @@ msgstr "Encaisse, placements, comptes débiteurs et autres actifs pouvant être msgid "Category" msgstr "Catégorie" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:558 +msgid "Census context makes municipal totals easier to compare, while recognizing that municipal boundaries and service responsibilities differ." +msgstr "Le contexte du recensement facilite la comparaison des totaux municipaux, tout en reconnaissant que les limites municipales et les responsabilités de service diffèrent." + #: src/components/first-nations/RemunerationOverview.tsx:401 msgid "Chief" msgstr "Chef" @@ -448,6 +526,10 @@ msgstr "Co-demandeur(s)" msgid "Collaboration Type" msgstr "Type de collaboration" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:555 +msgid "Community context" +msgstr "Contexte communautaire" + #: src/app/[lang]/(main)/first-nations/remuneration/page.tsx:34 msgid "Compare remuneration data across First Nations from annual reports published under the FNFTA." msgstr "Comparez les données de rémunération des Premières Nations à partir des rapports annuels publiés en vertu de la LTFPN." @@ -494,8 +576,8 @@ msgstr "revenus fédéraux consolidés (vol. I)" msgid "consolidated federal spending (Vol I)" msgstr "dépenses fédérales consolidées (vol. I)" -#: src/components/MainLayout/_components/DesktopNav.tsx:310 -#: src/components/MainLayout/_components/MobileMenu.tsx:195 +#: src/components/MainLayout/_components/DesktopNav.tsx:318 +#: src/components/MainLayout/_components/MobileMenu.tsx:204 #: src/components/MainLayout/Footer.tsx:54 msgid "Contact" msgstr "Contact" @@ -574,6 +656,10 @@ msgstr "Réductions des dépenses ministérielles" msgid "Description" msgstr "Description" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:277 +msgid "Details" +msgstr "Détails" + #: src/components/Sankey/BudgetSankey.tsx:433 msgid "Direct program expenses" msgstr "Dépenses directes des programmes" @@ -716,9 +802,15 @@ msgstr "Résultats attendus" #: src/components/first-nations/RemunerationOverview.tsx:651 #: src/components/first-nations/RemunerationOverview.tsx:797 #: src/components/first-nations/RemunerationTable.tsx:259 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:183 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:431 msgid "Expenses" msgstr "Dépenses" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:589 +msgid "Expenses per resident" +msgstr "Charges par habitant" + #: 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 "Explorez le budget fédéral du Canada avec des visualisations interactives et une analyse détaillée des revenus et dépenses gouvernementales." @@ -739,7 +831,7 @@ msgstr "Explorez les données financières des Premières Nations à travers le msgid "Explore financial data from First Nations across Canada. View statements of operations, financial positions, and remuneration from annual reports published under the First Nations Financial Transparency Act (FNFTA)." msgstr "Explorez les données financières des Premières Nations à travers le Canada. Consultez les états des résultats, les situations financières et la rémunération à partir des rapports annuels publiés en vertu de la Loi sur la transparence financière des Premières Nations (LTFPN)." -#: src/components/HeroButtons.tsx:110 +#: src/components/HeroButtons.tsx:118 msgid "Explore First Nations Spending" msgstr "Explorer les dépenses des Premières Nations" @@ -763,6 +855,14 @@ msgstr "Explorer d’autres ministères fédéraux" msgid "Explore Provincial Spending" msgstr "Explorer les dépenses provinciales" +#: src/app/[lang]/(main)/municipal/page.tsx:44 +msgid "Explore revenue, expenses, assets, liabilities, and accumulated surplus reported by municipalities across Canada." +msgstr "Explorez les revenus, les charges, les actifs, les passifs et l’excédent accumulé déclarés par les municipalités partout au Canada." + +#: src/app/[lang]/(main)/municipal/page.tsx:22 +msgid "Explore reviewed financial statement data for municipalities across Canada." +msgstr "Explorez les données révisées des états financiers des municipalités partout au Canada." + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:137 msgid "External ID" msgstr "ID externe" @@ -771,6 +871,10 @@ msgstr "ID externe" msgid "Facts about Spending" msgstr "Faits sur les dépenses" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:293 +msgid "Fail" +msgstr "Échec" + #: src/app/[lang]/(main)/articles/page.tsx:55 msgid "Featured Articles" msgstr "Articles en vedette" @@ -836,6 +940,10 @@ msgstr "Total fédéral" msgid "Federal workforce" msgstr "Effectif de la fonction publique fédérale" +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:57 +msgid "Filter by province or territory" +msgstr "Filtrer par province ou territoire" + #: src/components/DepartmentList.tsx:15 msgid "Finance Canada" msgstr "Finance Canada" @@ -868,15 +976,29 @@ msgstr "Passifs financiers moins actifs financiers" msgid "Financial liabilities minus financial assets. A negative value indicates net financial assets." msgstr "Passifs financiers moins actifs financiers. Une valeur négative indique des actifs financiers nets." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:604 +msgid "Financial position" +msgstr "Situation financière" + #. placeholder {0}: jurisdiction.financialYear #: src/components/JurisdictionPageContent.tsx:399 msgid "Financial Position {0}" msgstr "Situation financière {0}" +#. placeholder {0}: statement.fiscal_year +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:475 +msgid "Financial summary for {0}" +msgstr "Sommaire financier pour {0}" + #: src/components/first-nations/FirstNationsPageContent.tsx:343 msgid "Financial Summary FY {fiscalYear}" msgstr "Résumé financier EF {fiscalYear}" +#. placeholder {0}: statement.fiscal_year +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:594 +msgid "Financial year {0}" +msgstr "Exercice financier {0}" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:398 #: src/app/[lang]/(main)/tax-visualizer/page.tsx:635 msgid "First" @@ -888,8 +1010,8 @@ msgid "First Nation" msgstr "Première Nation" #: src/app/[lang]/(main)/first-nations/[bcid]/page.tsx:59 -#: src/components/MainLayout/_components/DesktopNav.tsx:188 -#: src/components/MainLayout/_components/MobileMenu.tsx:147 +#: src/components/MainLayout/_components/DesktopNav.tsx:196 +#: src/components/MainLayout/_components/MobileMenu.tsx:156 msgid "First Nations" msgstr "Premières Nations" @@ -951,6 +1073,10 @@ msgstr "EF" msgid "FY {0} Revenue and Spending" msgstr "Revenus et dépenses de l’exercice {0}" +#: src/components/FinancialYearSelector.tsx:38 +msgid "FY {year}" +msgstr "EF {year}" + #. placeholder {0}: jurisdiction.name #. placeholder {1}: jurisdiction.name #: src/components/JurisdictionPageContent.tsx:348 @@ -961,7 +1087,7 @@ msgstr "Obtenez des analyses basées sur les données sur la façon dont les rev msgid "Get data-driven insights into how the federal government’s revenue and spending affect Canadian lives and programs." msgstr "Découvrez, données à l’appui, comment les revenus et les dépenses du gouvernement fédéral influent sur la vie des Canadiens et sur les programmes." -#: src/components/MainLayout/_components/DesktopNav.tsx:302 +#: src/components/MainLayout/_components/DesktopNav.tsx:310 #: src/components/MainLayout/Footer.tsx:49 msgid "Get Involved" msgstr "Impliquez-vous" @@ -1094,6 +1220,10 @@ msgstr "Comment les pages sur les dépenses fédérales sont produites à partir msgid "How ministry and Sankey totals are allocated" msgstr "Comment les totaux ministériels et du diagramme de Sankey sont répartis" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:478 +msgid "How much the municipality reported earning and spending during the fiscal year." +msgstr "Les revenus et les dépenses déclarés par la municipalité au cours de l’exercice financier." + #: src/app/[lang]/(main)/federal/spending/[year]/page.tsx:66 msgid "How the Government of Canada spent its budget in fiscal year {fy}, by department, theme, and program." msgstr "Comment le gouvernement du Canada a dépensé son budget durant l'exercice {fy}, par ministère, thème et programme." @@ -1145,6 +1275,10 @@ msgstr "Analyse approfondie et perspectives sur les dépenses gouvernementales c msgid "Income Tax Revenues" msgstr "Revenus de l'impôt sur le revenu" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:228 +msgid "Independent verification completed" +msgstr "Vérification indépendante terminée" + #: src/components/DepartmentList.tsx:21 msgid "Indigenous Services Canada + Crown-Indigenous Relations and Northern Affairs Canada" msgstr "Services aux Autochtones Canada + Relations Couronne-Autochtones et Affaires du Nord Canada" @@ -1190,9 +1324,9 @@ msgstr "Ça ne doit pas être comme ça." msgid "Item" msgstr "Élément" -#: src/components/MainLayout/_components/DesktopNav.tsx:330 +#: src/components/MainLayout/_components/DesktopNav.tsx:338 #: src/components/MainLayout/_components/MobileMenu.tsx:22 -#: src/components/MainLayout/_components/MobileMenu.tsx:211 +#: src/components/MainLayout/_components/MobileMenu.tsx:220 msgid "Join Build Canada" msgstr "Rejoindre Build Canada" @@ -1204,6 +1338,10 @@ msgstr "Dates clés" msgid "Keywords" msgstr "Mots-clés" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:572 +msgid "Land area" +msgstr "Superficie terrestre" + #: src/components/first-nations/FirstNationsPageContent.tsx:395 msgid "Land Claims" msgstr "Revendications territoriales" @@ -1290,6 +1428,18 @@ msgstr "Plus à venir..." msgid "Municipal" msgstr "Municipal" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:574 +msgid "Municipal census subdivision" +msgstr "Subdivision de recensement municipale" + +#: src/app/[lang]/(main)/municipal/page.tsx:41 +msgid "Municipal Financial Statements" +msgstr "États financiers municipaux" + +#: src/app/[lang]/(main)/municipal/page.tsx:21 +msgid "Municipal Financial Statements | Canada Spends" +msgstr "États financiers municipaux | Canada Spends" + #: src/components/first-nations/RemunerationOverview.tsx:788 #: src/components/first-nations/RemunerationTable.tsx:114 msgid "Name" @@ -1324,6 +1474,10 @@ msgstr "Dette nette" msgid "Net Debt is what remains after subtracting financial assets (like cash and investments) from the Total Debt. It represents the debt that isn't immediately covered by liquid assets." msgstr "La dette nette est ce qui reste après avoir soustrait les actifs financiers (comme la trésorerie et les investissements) de la dette totale. Elle représente la dette qui n'est pas immédiatement couverte par des actifs liquides." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:35 +msgid "Net financial assets (debt)" +msgstr "Actifs financiers nets (dette)" + #: src/components/Sankey/BudgetSankey.tsx:517 msgid "Net Foreign Exchange Revenue and Return on Investments" msgstr "Revenus nets de change et rendement des investissements" @@ -1372,6 +1526,10 @@ msgstr "Aucune entrée individuelle disponible." msgid "No matching line items." msgstr "Aucun poste correspondant." +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:121 +msgid "No municipalities match those filters." +msgstr "Aucune municipalité ne correspond à ces filtres." + #: src/components/first-nations/FirstNationsNotes.tsx:20 msgid "No notes available." msgstr "Aucune note disponible." @@ -1464,6 +1622,10 @@ msgstr "Pop. dans réserve" msgid "Open Tor Browser and wait for it to connect." msgstr "Ouvrez le navigateur Tor et attendez qu'il se connecte." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:41 +msgid "Opening accumulated surplus" +msgstr "Excédent accumulé d’ouverture" + #: src/app/[lang]/(main)/budget/page.tsx:195 #: src/app/[lang]/(main)/federal/budget/page.tsx:234 msgid "Operational Spend" @@ -1515,15 +1677,26 @@ msgstr "Autres paiements de transfert" msgid "Our government is going to have to make hard choices about our nation's spending to ensure we can invest in creating a competitive, resilient, and independent nation. We care about giving Canadians the facts about spending so they can engage in this conversation with elected officials." msgstr "Notre gouvernement devra faire des choix difficiles concernant les dépenses de notre nation pour assurer que nous puissions investir dans la création d'une nation compétitive, résiliente et indépendante. Nous tenons à donner aux Canadiens les faits sur les dépenses afin qu'ils puissent participer à cette conversation avec les élus." -#: src/components/MainLayout/_components/DesktopNav.tsx:202 -#: src/components/MainLayout/_components/MobileMenu.tsx:155 +#: src/components/MainLayout/_components/DesktopNav.tsx:210 +#: src/components/MainLayout/_components/MobileMenu.tsx:164 msgid "Overview" msgstr "Aperçu" +#. placeholder {0}: fact.source_page +#. placeholder {0}: item.source_page +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:147 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:453 +msgid "page {0}" +msgstr "page {0}" + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:225 msgid "Parenthesized negatives, internal consolidation adjustments, and total or subtotal rows are flagged and excluded from Sankey leaves to avoid double counting, but they are retained in the line-item tables so the full detail remains available." msgstr "Les montants négatifs entre parenthèses, les rajustements de consolidation internes et les lignes de total ou de sous-total sont signalés et exclus des feuilles du diagramme de Sankey afin d’éviter le double comptage, mais ils sont conservés dans les tableaux de postes détaillés afin que le détail complet demeure accessible." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:289 +msgid "Pass" +msgstr "Réussi" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:338 msgid "Payment Amount" msgstr "Montant du paiement" @@ -1567,6 +1740,14 @@ msgstr "Tarification de la pollution" msgid "Pollution pricing proceeds" msgstr "Produits de la tarification de la pollution" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:566 +msgid "Population" +msgstr "Population" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:579 +msgid "Population density" +msgstr "Densité de population" + #: src/components/first-nations/RemunerationOverview.tsx:791 #: src/components/first-nations/RemunerationTable.tsx:107 msgid "Position" @@ -1598,6 +1779,10 @@ msgstr "Nom du programme" msgid "Program Type" msgstr "Type de programme" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:540 +msgid "Program, service, administration, financing, and amortization expenses." +msgstr "Charges liées aux programmes, aux services, à l’administration, au financement et à l’amortissement." + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:235 msgid "Project Number" msgstr "Numéro de projet" @@ -1708,8 +1893,8 @@ msgid "Regions" msgstr "Régions" #: src/components/first-nations/RemunerationTable.tsx:233 -#: src/components/MainLayout/_components/DesktopNav.tsx:210 -#: src/components/MainLayout/_components/MobileMenu.tsx:166 +#: src/components/MainLayout/_components/DesktopNav.tsx:218 +#: src/components/MainLayout/_components/MobileMenu.tsx:175 msgid "Remuneration" msgstr "Rémunération" @@ -1717,6 +1902,15 @@ msgstr "Rémunération" msgid "Remuneration and Expenses" msgstr "Rémunération et dépenses" +#. placeholder {0}: fact.raw_label +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:137 +msgid "Reported as “{0}”" +msgstr "Déclaré comme « {0} »" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:409 +msgid "Reported revenue and expense details" +msgstr "Détail des revenus et des charges déclarés" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:277 msgid "Reporting Organization" msgstr "Organisation déclarante" @@ -1726,6 +1920,14 @@ msgstr "Organisation déclarante" msgid "Research Subject" msgstr "Sujet de recherche" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:584 +msgid "Residents per km²" +msgstr "Habitants par km²" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:271 +msgid "Result" +msgstr "Résultat" + #: src/app/[lang]/(main)/search/[database]/[id]/page.tsx:267 msgid "Results Achieved" msgstr "Résultats obtenus" @@ -1736,6 +1938,8 @@ msgstr "Prestations de retraite" #: src/app/[lang]/(main)/budget/page.tsx:184 #: src/app/[lang]/(main)/federal/budget/page.tsx:223 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:177 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:429 #: src/components/Sankey/BudgetSankey.tsx:456 msgid "Revenue" msgstr "Revenus" @@ -1748,6 +1952,25 @@ msgstr "Revenus et dépenses EF {fiscalYear}" msgid "revenue minus spending (Vol I)" msgstr "revenus moins dépenses (vol. I)" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:488 +msgid "Revenue remaining after expenses" +msgstr "Revenus restant après les charges" + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:383 +msgid "Revenue sources flow in from the left and spending by service or function flows out on the right. Revenue offsets and losses are shown as outflows, while expense recoveries are shown as inflows. Values come from detailed schedules in the audited financial statements." +msgstr "Les sources de revenus entrent par la gauche et les dépenses par service ou fonction sortent par la droite. Les réductions de revenus et les pertes sont présentées comme des sorties, tandis que les recouvrements de dépenses sont présentés comme des entrées. Les valeurs proviennent des tableaux détaillés des états financiers audités." + +#. placeholder {0}: verification.reviewed_by +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:234 +msgid "Reviewed by {0} on {reviewedAt}" +msgstr "Examiné par {0} le {reviewedAt}" + +#. placeholder {0}: municipality.name +#. placeholder {1}: municipality.province_name +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:351 +msgid "Reviewed financial statement data for {0} in {1}, for the fiscal year ending {fiscalYearEnd}." +msgstr "Données révisées des états financiers de {0}, en {1}, pour l’exercice financier se terminant le {fiscalYearEnd}." + #: src/components/FederalWorkforceStrip.tsx:89 msgid "salaries and benefits (standard object)" msgstr "traitements et avantages sociaux (article courant)" @@ -1768,6 +1991,11 @@ msgstr "Conservez votre code de réception en lieu sûr—vous en aurez besoin p msgid "Search line items…" msgstr "Rechercher des postes…" +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:45 +#: src/components/municipal/MunicipalFinancialStatementsSearch.tsx:51 +msgid "Search municipalities" +msgstr "Rechercher des municipalités" + #: src/app/[lang]/(main)/whistleblowers/page.tsx:153 msgid "Security Tips" msgstr "Conseils de sécurité" @@ -1809,6 +2037,10 @@ msgstr "Affichage de {0} sur {1} Premières Nations" msgid "Showing {0} of {1} results" msgstr "Affichage de {0} sur {1} résultats" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:291 +msgid "Skipped" +msgstr "Ignoré" + #: src/components/Sankey/BudgetSankey.tsx:347 msgid "Social Security" msgstr "Sécurité sociale" @@ -1827,6 +2059,10 @@ msgstr "Source" msgid "Source and licence" msgstr "Source et licence" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:641 +msgid "Source and methodology" +msgstr "Source et méthodologie" + #: src/components/first-nations/RemunerationOverview.tsx:601 msgid "Source PDF" msgstr "PDF source" @@ -1879,9 +2115,9 @@ msgstr "Dépenses par ministère, exercice {0}" msgid "Spending data is not yet available for this province and year combination. The income tax calculation above is still accurate based on {year} tax rates." msgstr "Les données de dépenses ne sont pas encore disponibles pour cette combinaison de province et d'année. Le calcul de l'impôt sur le revenu ci-dessus reste exact selon les taux d'imposition de {year}." -#: src/components/MainLayout/_components/DesktopNav.tsx:249 -#: src/components/MainLayout/_components/DesktopNav.tsx:268 -#: src/components/MainLayout/_components/MobileMenu.tsx:181 +#: src/components/MainLayout/_components/DesktopNav.tsx:257 +#: src/components/MainLayout/_components/DesktopNav.tsx:276 +#: src/components/MainLayout/_components/MobileMenu.tsx:190 msgid "Spending Database" msgstr "Base de données des dépenses" @@ -1986,16 +2222,24 @@ msgstr "Visualiseur d'impôt | Canada Spends" msgid "Tax Year" msgstr "Année d'imposition" -#: src/components/MainLayout/_components/DesktopNav.tsx:241 -#: src/components/MainLayout/_components/DesktopNav.tsx:261 -#: src/components/MainLayout/_components/MobileMenu.tsx:174 +#: src/components/MainLayout/_components/DesktopNav.tsx:249 +#: src/components/MainLayout/_components/DesktopNav.tsx:269 +#: src/components/MainLayout/_components/MobileMenu.tsx:183 msgid "Taxes" msgstr "Impôts" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:515 +msgid "Taxes, transfers, fees, services, and other reported income." +msgstr "Impôts, transferts, droits, services et autres revenus déclarés." + #: src/components/Sankey/BudgetSankey.tsx:395 msgid "Territorial Formula Financing" msgstr "Financement des territoires par formule" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:274 +msgid "Test" +msgstr "Test" + #: src/app/[lang]/(main)/about/faq.tsx:61 msgid "The best way to support us is to engage with our content. Like, share, and comment on our posts on social media. Even better, share our work with a friend and encourage them to do the same. Together, we can expand the conversation and reach more people with these important facts." msgstr "La meilleure façon de nous soutenir est d'interagir avec notre contenu. Aimez, partagez et commentez nos publications sur les réseaux sociaux. Mieux encore, partagez notre travail avec un ami et encouragez-le à faire de même. Ensemble, nous pouvons élargir la conversation et atteindre plus de gens avec ces faits importants." @@ -2012,10 +2256,20 @@ msgstr "La différence entre les revenus et les dépenses. Un excédent indique msgid "The difference between total revenue and total expenses. A surplus indicates revenue exceeded expenses." msgstr "La différence entre les revenus totaux et les dépenses totales. Un excédent indique que les revenus ont dépassé les dépenses." +#. placeholder {0}: formatNumber(context.population, lang, 0) +#. placeholder {1}: formatNumber(context.area_sq_km || 0, lang) +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:359 +msgid "The municipality serves a {censusYear} Census population of {0} across {1} square kilometres." +msgstr "La municipalité dessert une population de {0} habitants selon le Recensement de {censusYear}, sur une superficie de {1} kilomètres carrés." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:194 msgid "The previous hand-authored pages contained curated aggregations — for example, hand-massaged negative leaves and thematic groupings that split a single ministry across categories. Every node where the generated tree differs from the previous values is enumerated in the parity report with a reason code (basis, mapping, rounding, or source correction)." msgstr "Les pages précédentes, rédigées à la main, contenaient des agrégations organisées — par exemple, des feuilles négatives ajustées manuellement et des regroupements thématiques répartissant un même ministère entre plusieurs catégories. Chaque nœud où l’arborescence générée diffère des valeurs précédentes est recensé dans le rapport de parité avec un code de motif (base comptable, mappage, arrondissement ou correction à la source)." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:490 +msgid "The reported difference between annual revenue and expenses, including any separately presented adjustments." +msgstr "L’écart déclaré entre les revenus et les charges annuels, y compris les ajustements présentés séparément." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:253 msgid "The Sankey uses a curated thematic tree. A maintained mapping assigns each Public Accounts line to a thematic node, mostly at the ministry or organization level with line-level overrides where needed. Each node is truncated to its largest children with the remainder rolled into an “Other” aggregate." msgstr "Le diagramme de Sankey utilise une arborescence thématique organisée. Un mappage tenu à jour attribue chaque ligne des Comptes publics à un nœud thématique, principalement au niveau du ministère ou de l’organisation, avec des exceptions au niveau des lignes au besoin. Chaque nœud est tronqué à ses plus grands enfants, le reste étant regroupé dans un agrégat « Autre »." @@ -2036,6 +2290,10 @@ msgstr "Mappage thématique" msgid "Theme" msgstr "Thème" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:412 +msgid "These are the detailed rows extracted from the audited statement. This table preserves every signed amount, including negative and consolidation entries that the Sankey repositions as offsets or recoveries." +msgstr "Voici les postes détaillés extraits de l’état financier audité. Ce tableau conserve chaque montant avec son signe, y compris les écritures négatives et de consolidation que le diagramme de Sankey repositionne comme réductions ou recouvrements." + #: src/app/[lang]/(main)/federal/spending/methodology/page.tsx:57 msgid "These pages are generated from the Public Accounts of Canada. This page documents the accounting bases, curation choices, and known differences so the numbers are traceable to source." msgstr "Ces pages sont générées à partir des Comptes publics du Canada. La présente page documente les bases comptables, les choix de curation et les écarts connus afin que les chiffres soient traçables jusqu’à la source." @@ -2061,7 +2319,7 @@ msgstr "Cette visualisation montre comment vos contributions d'impôt sur le rev msgid "to help us support our mission." msgstr "pour nous aider à soutenir notre mission." -#: src/components/MainLayout/_components/DesktopNav.tsx:227 +#: src/components/MainLayout/_components/DesktopNav.tsx:235 msgid "Tools" msgstr "Outils" @@ -2105,6 +2363,11 @@ msgstr "La dette totale est la dette complète en cours du gouvernement. C'est l msgid "Total difference" msgstr "Écart total" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:45 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:524 +msgid "Total expenses" +msgstr "Total des charges" + #: src/components/first-nations/FirstNationsPageContent.tsx:261 msgid "Total Expenses" msgstr "Dépenses totales" @@ -2117,10 +2380,18 @@ msgstr "Dépenses totales pour l'exercice {fiscalYear}" msgid "Total Federal Tax" msgstr "Total impôt fédéral" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:31 +msgid "Total financial assets" +msgstr "Total des actifs financiers" + #: src/components/first-nations/FinancialPositionStats.tsx:168 msgid "Total Financial Assets" msgstr "Total des actifs financiers" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:33 +msgid "Total liabilities" +msgstr "Total des passifs" + #: src/components/first-nations/FinancialPositionStats.tsx:191 msgid "Total Liabilities" msgstr "Total des passifs" @@ -2129,6 +2400,10 @@ msgstr "Total des passifs" msgid "Total net assets" msgstr "Total des actifs nets" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:37 +msgid "Total non-financial assets" +msgstr "Total des actifs non financiers" + #: src/components/first-nations/ClaimsTable.tsx:148 msgid "Total Payments" msgstr "Total des paiements" @@ -2147,6 +2422,11 @@ msgstr "Revenus totaux de l'impôt foncier divisés par la population. Source de msgid "Total Remuneration" msgstr "Rémunération totale" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:43 +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:499 +msgid "Total revenue" +msgstr "Total des revenus" + #: src/components/first-nations/FirstNationsPageContent.tsx:241 #: src/components/JurisdictionPageContent.tsx:160 msgid "Total Revenue" @@ -2241,6 +2521,14 @@ msgstr "Unités" msgid "Use a computer you trust, not one provided by your employer." msgstr "Utilisez un ordinateur en qui vous avez confiance, pas un ordinateur fourni par votre employeur." +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:644 +msgid "Values are extracted from the municipality’s published financial statements and independently reviewed before publication. Page links point to the reported figure in the source document." +msgstr "Les valeurs sont extraites des états financiers publiés par la municipalité et font l’objet d’une révision indépendante avant leur publication. Les liens de page renvoient au montant déclaré dans le document source." + +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:230 +msgid "Verification requires attention" +msgstr "La vérification nécessite une attention particulière" + #: src/components/DepartmentList.tsx:45 msgid "Veterans Affairs" msgstr "Anciens Combattants" @@ -2250,6 +2538,11 @@ msgstr "Anciens Combattants" msgid "View {year} financial statements for {0}{provinceSuffix}. Includes statement of operations, financial position, and remuneration data from the First Nations Financial Transparency Act annual report." msgstr "Consultez les états financiers {year} pour {0}{provinceSuffix}. Comprend l'état des résultats, la situation financière et les données de rémunération du rapport annuel de la Loi sur la transparence financière des Premières Nations." +#. placeholder {0}: verification.summary.total +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:262 +msgid "View all {0} verification checks" +msgstr "Voir les {0} contrôles de vérification" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:858 #: src/components/TaxBreakdownAccordion.tsx:100 msgid "View detailed tax breakdown" @@ -2264,7 +2557,7 @@ msgstr "Voir les détails du budget fédéral 2025" msgid "View financial data for {0}{provinceSuffix} from the First Nations Financial Transparency Act annual reports." msgstr "Consultez les données financières pour {0}{provinceSuffix} à partir des rapports annuels de la Loi sur la transparence financière des Premières Nations." -#: src/components/first-nations/FirstNationsYearSelector.tsx:26 +#: src/components/FinancialYearSelector.tsx:22 msgid "View Other Fiscal Years" msgstr "Voir les autres exercices financiers" @@ -2272,6 +2565,10 @@ msgstr "Voir les autres exercices financiers" msgid "View source data" msgstr "Voir les données sources" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:658 +msgid "View source financial statements" +msgstr "Voir les états financiers sources" + #: src/app/[lang]/(main)/budget/page.tsx:242 #: src/app/[lang]/(main)/federal/budget/page.tsx:281 #: src/app/[lang]/(main)/federal/spending/[year]/page.tsx:231 @@ -2372,14 +2669,18 @@ msgstr "Qu'est-ce qui fait une bonne information?" msgid "Where do you get the data on your website?" msgstr "D'où proviennent les données sur votre site web?" +#: src/components/municipal/MunicipalFinancialStatementContent.tsx:373 +msgid "Where the money came from and where it went" +msgstr "D’où vient l’argent et où il va" + #: src/app/[lang]/(main)/tax-visualizer/page.tsx:826 #: src/app/[lang]/(main)/tax-visualizer/page.tsx:880 msgid "Where Your Tax Dollars Go" msgstr "Où vont vos dollars d'impôt" #: src/app/[lang]/(main)/whistleblowers/page.tsx:44 -#: src/components/MainLayout/_components/DesktopNav.tsx:318 -#: src/components/MainLayout/_components/MobileMenu.tsx:202 +#: src/components/MainLayout/_components/DesktopNav.tsx:326 +#: src/components/MainLayout/_components/MobileMenu.tsx:211 #: src/components/MainLayout/Footer.tsx:59 msgid "Whistleblowers" msgstr "Lanceurs d'alerte"