translation

This commit is contained in:
Gal Podlipnik 2025-06-17 03:51:06 +02:00
parent ea7a8cd5de
commit bef1e9c398
13 changed files with 670 additions and 244 deletions

638
package-lock.json generated
View File

@ -17,7 +17,10 @@
"@angular/forms": "~20.0.0", "@angular/forms": "~20.0.0",
"@angular/platform-browser": "~20.0.0", "@angular/platform-browser": "~20.0.0",
"@angular/router": "~20.0.0", "@angular/router": "~20.0.0",
"@formatjs/intl": "^3.1.6",
"@mmstack/primitives": "^20.0.0", "@mmstack/primitives": "^20.0.0",
"@mmstack/router-core": "^20.0.0",
"@mmstack/translate": "^20.0.0",
"@primeng/themes": "^19.1.3", "@primeng/themes": "^19.1.3",
"express": "^4.21.2", "express": "^4.21.2",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
@ -947,32 +950,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/less": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz",
"integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==",
"license": "Apache-2.0",
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/less-loader": { "node_modules/@angular-devkit/build-angular/node_modules/less-loader": {
"version": "12.3.0", "version": "12.3.0",
"resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz", "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz",
@ -1195,16 +1172,27 @@
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/readdirp": { "node_modules/@angular-devkit/build-angular/node_modules/readdirp": {
"version": "4.1.2", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14.18.0" "node": ">=8.6"
}, },
"funding": { "funding": {
"type": "individual", "url": "https://github.com/sponsors/jonschlinkert"
"url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/restore-cursor": { "node_modules/@angular-devkit/build-angular/node_modules/restore-cursor": {
@ -1243,21 +1231,6 @@
"@parcel/watcher": "^2.4.1" "@parcel/watcher": "^2.4.1"
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/sass/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/signal-exit": { "node_modules/@angular-devkit/build-angular/node_modules/signal-exit": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@ -1443,6 +1416,42 @@
} }
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.9", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
@ -2429,21 +2438,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@angular/build/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/build/node_modules/esbuild": { "node_modules/@angular/build/node_modules/esbuild": {
"version": "0.25.5", "version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
@ -2484,19 +2478,6 @@
"@esbuild/win32-x64": "0.25.5" "@esbuild/win32-x64": "0.25.5"
} }
}, },
"node_modules/@angular/build/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/build/node_modules/sass": { "node_modules/@angular/build/node_modules/sass": {
"version": "1.88.0", "version": "1.88.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz",
@ -2772,22 +2753,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/@angular/compiler-cli/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/compiler-cli/node_modules/cliui": { "node_modules/@angular/compiler-cli/node_modules/cliui": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
@ -2817,20 +2782,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@angular/compiler-cli/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/compiler-cli/node_modules/string-width": { "node_modules/@angular/compiler-cli/node_modules/string-width": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@ -5313,6 +5264,78 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz",
"integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==",
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/intl-localematcher": "0.6.1",
"decimal.js": "^10.4.3",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz",
"integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==",
"license": "MIT",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/icu-skeleton-parser": "1.8.14",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.8.14",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz",
"integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==",
"license": "MIT",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/intl": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-3.1.6.tgz",
"integrity": "sha512-tDkXnA4qpIFcDWac8CyVJq6oW8DR7W44QDUBsfXWIIJD/FYYen0QoH46W7XsVMFfPOVKkvbufjboZrrWbEfmww==",
"license": "MIT",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/icu-messageformat-parser": "2.11.2",
"intl-messageformat": "10.7.16",
"tslib": "^2.8.0"
},
"peerDependencies": {
"typescript": "^5.6.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz",
"integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -6146,6 +6169,35 @@
"@angular/core": "~20.0.3" "@angular/core": "~20.0.3"
} }
}, },
"node_modules/@mmstack/router-core": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@mmstack/router-core/-/router-core-20.0.0.tgz",
"integrity": "sha512-804EpWd6m/Jiq1sneAhy3sudFQu7rZhLb5ctC/1WF5VFyVQemCKoRaSysNiY1jqVWaUC4EtGGg2sZA0j9iW2Zg==",
"license": "MIT",
"dependencies": {
"@mmstack/primitives": "^20.0.0",
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/core": "~20.0.3",
"@angular/platform-browser": "~20.0.3",
"@angular/router": "~20.0.3",
"rxjs": "~7.8.2"
}
},
"node_modules/@mmstack/translate": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@mmstack/translate/-/translate-20.0.0.tgz",
"integrity": "sha512-ClRBJnHP7evYGXuXLyvlKzd4Bn3D7QVyz8oc5wt8AmELal5ikj9H88OrCaCr9o/hb/T855vtTvkJf8nNz+bjZg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/core": "~20.0.3",
"@formatjs/intl": "~3.1.6"
}
},
"node_modules/@modern-js/node-bundle-require": { "node_modules/@modern-js/node-bundle-require": {
"version": "2.67.6", "version": "2.67.6",
"resolved": "https://registry.npmjs.org/@modern-js/node-bundle-require/-/node-bundle-require-2.67.6.tgz", "resolved": "https://registry.npmjs.org/@modern-js/node-bundle-require/-/node-bundle-require-2.67.6.tgz",
@ -8204,6 +8256,33 @@
"webpack-subresource-integrity": "^5.1.0" "webpack-subresource-integrity": "^5.1.0"
} }
}, },
"node_modules/@nx/webpack/node_modules/less": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
"integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=6"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/@nx/webpack/node_modules/postcss-loader": { "node_modules/@nx/webpack/node_modules/postcss-loader": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz",
@ -8227,6 +8306,17 @@
"webpack": "^5.0.0" "webpack": "^5.0.0"
} }
}, },
"node_modules/@nx/webpack/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@nx/workspace": { "node_modules/@nx/workspace": {
"version": "21.2.0", "version": "21.2.0",
"resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.2.0.tgz", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.2.0.tgz",
@ -9113,6 +9203,31 @@
"@rspack/core": "*" "@rspack/core": "*"
} }
}, },
"node_modules/@rspack/dev-server/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/@rspack/dev-server/node_modules/http-proxy-middleware": { "node_modules/@rspack/dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.9", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
@ -9138,6 +9253,32 @@
} }
} }
}, },
"node_modules/@rspack/dev-server/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rspack/dev-server/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/@rspack/lite-tapable": { "node_modules/@rspack/lite-tapable": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz",
@ -11334,27 +11475,18 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "readdirp": "^4.0.1"
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}, },
"engines": { "engines": {
"node": ">= 8.10.0" "node": ">= 14.16.0"
}, },
"funding": { "funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
} }
}, },
"node_modules/chownr": { "node_modules/chownr": {
@ -11866,6 +11998,16 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/cosmiconfig/node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/cron-parser": { "node_modules/cron-parser": {
"version": "4.9.0", "version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
@ -12200,6 +12342,12 @@
} }
} }
}, },
"node_modules/decimal.js": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
"license": "MIT"
},
"node_modules/deep-equal": { "node_modules/deep-equal": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
@ -13720,6 +13868,31 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@ -13755,6 +13928,32 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/fork-ts-checker-webpack-plugin/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@ -14616,6 +14815,18 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/intl-messageformat": {
"version": "10.7.16",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz",
"integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==",
"license": "BSD-3-Clause",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/icu-messageformat-parser": "2.11.2",
"tslib": "^2.8.0"
}
},
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
@ -15422,10 +15633,9 @@
} }
}, },
"node_modules/less": { "node_modules/less": {
"version": "4.1.3", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", "resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz",
"integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", "integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"copy-anything": "^2.0.1", "copy-anything": "^2.0.1",
@ -15436,7 +15646,7 @@
"lessc": "bin/lessc" "lessc": "bin/lessc"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=14"
}, },
"optionalDependencies": { "optionalDependencies": {
"errno": "^0.1.1", "errno": "^0.1.1",
@ -15473,7 +15683,6 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"optional": true, "optional": true,
"engines": { "engines": {
@ -17147,19 +17356,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nx/node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@ -18712,27 +18908,16 @@
} }
}, },
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8.6" "node": ">= 14.18.0"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/jonschlinkert" "type": "individual",
"url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/reflect-metadata": { "node_modules/reflect-metadata": {
@ -19487,36 +19672,6 @@
} }
} }
}, },
"node_modules/sass/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sass/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sax": { "node_modules/sax": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@ -20839,6 +20994,31 @@
} }
} }
}, },
"node_modules/ts-checker-rspack-plugin/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/ts-checker-rspack-plugin/node_modules/memfs": { "node_modules/ts-checker-rspack-plugin/node_modules/memfs": {
"version": "4.17.2", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
@ -20875,6 +21055,32 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/ts-checker-rspack-plugin/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/ts-checker-rspack-plugin/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/ts-loader": { "node_modules/ts-loader": {
"version": "9.5.2", "version": "9.5.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz",
@ -21930,6 +22136,31 @@
} }
} }
}, },
"node_modules/webpack-dev-server/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/webpack-dev-server/node_modules/define-lazy-prop": { "node_modules/webpack-dev-server/node_modules/define-lazy-prop": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
@ -22013,6 +22244,32 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/webpack-dev-server/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/webpack-dev-server/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/webpack-merge": { "node_modules/webpack-merge": {
"version": "5.10.0", "version": "5.10.0",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
@ -22226,13 +22483,16 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "1.10.2", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": { "engines": {
"node": ">= 6" "node": ">= 14.6"
} }
}, },
"node_modules/yargs": { "node_modules/yargs": {

View File

@ -17,7 +17,10 @@
"@angular/forms": "~20.0.0", "@angular/forms": "~20.0.0",
"@angular/platform-browser": "~20.0.0", "@angular/platform-browser": "~20.0.0",
"@angular/router": "~20.0.0", "@angular/router": "~20.0.0",
"@formatjs/intl": "^3.1.6",
"@mmstack/primitives": "^20.0.0", "@mmstack/primitives": "^20.0.0",
"@mmstack/router-core": "^20.0.0",
"@mmstack/translate": "^20.0.0",
"@primeng/themes": "^19.1.3", "@primeng/themes": "^19.1.3",
"express": "^4.21.2", "express": "^4.21.2",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",

View File

@ -1,19 +1,21 @@
import { isPlatformBrowser } from '@angular/common'; import { Component, computed, inject, OnInit } from '@angular/core';
import {
Component,
computed,
inject,
OnInit,
PLATFORM_ID,
} from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router'; import { RouterLink, RouterOutlet } from '@angular/router';
import { stored } from '@mmstack/primitives'; import { stored } from '@mmstack/primitives';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { TooltipModule } from 'primeng/tooltip';
import { LocaleStore } from './translations/locale.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [RouterOutlet, RouterLink, ButtonModule], imports: [
RouterOutlet,
RouterLink,
ButtonModule,
OverlayPanelModule,
TooltipModule,
],
template: ` template: `
<header class="navbar"> <header class="navbar">
<div class="navbar-left"> <div class="navbar-left">
@ -32,6 +34,38 @@ import { ButtonModule } from 'primeng/button';
pTooltip="Toggle theme" pTooltip="Toggle theme"
tooltipPosition="bottom" tooltipPosition="bottom"
></button> ></button>
<!-- Language Selector Button -->
<button
pButton
type="button"
[label]="currentLocale() === 'en' ? 'EN' : 'SL'"
class="p-button-rounded p-button-text language-selector"
(click)="languagePanel.toggle($event)"
pTooltip="Change language"
tooltipPosition="bottom"
></button>
<!-- Language Selection Panel -->
<p-overlayPanel #languagePanel>
<div class="language-options">
<button
pButton
type="button"
label="English"
(click)="changeLanguage('en'); languagePanel.hide()"
[class.active]="currentLocale() === 'en'"
class="p-button-text w-full text-left"
></button>
<button
pButton
type="button"
label="Slovenian"
(click)="changeLanguage('sl'); languagePanel.hide()"
[class.active]="currentLocale() === 'sl'"
class="p-button-text w-full text-left"
></button>
</div>
</p-overlayPanel>
<img class="avatar" src="/placeholder-user.jpg" alt="User" /> <img class="avatar" src="/placeholder-user.jpg" alt="User" />
<span class="username">Gal Podlipnik</span> <span class="username">Gal Podlipnik</span>
</div> </div>
@ -123,7 +157,13 @@ import { ButtonModule } from 'primeng/button';
cursor: pointer; cursor: pointer;
} }
.dark-theme .theme-toggle-btn { .language-selector {
background: #fff !important;
color: var(--primary-color) !important;
cursor: pointer;
}
.dark-theme .language-selector {
background: #fff !important; background: #fff !important;
color: var(--primary-color) !important; color: var(--primary-color) !important;
border: 1.5px solid var(--primary-light); border: 1.5px solid var(--primary-light);
@ -174,27 +214,26 @@ export class App implements OnInit {
private readonly theme = stored('light', { private readonly theme = stored('light', {
key: 'theme', key: 'theme',
}); });
private readonly platformId = inject(PLATFORM_ID); private readonly locale = inject(LocaleStore);
private readonly isBrowser = isPlatformBrowser(this.platformId);
currentLocale = computed(() => this.locale.getCurrentLocale());
isDarkMode = computed(() => this.theme() === 'dark'); isDarkMode = computed(() => this.theme() === 'dark');
ngOnInit() { ngOnInit() {
if (this.isBrowser) { this.applyTheme();
this.applyTheme(); }
}
changeLanguage(locale: string) {
this.locale.setLocale(locale);
} }
toggleTheme() { toggleTheme() {
this.theme.set(this.isDarkMode() ? 'light' : 'dark'); this.theme.set(this.isDarkMode() ? 'light' : 'dark');
if (this.isBrowser) { this.applyTheme();
this.applyTheme();
}
} }
applyTheme() { applyTheme() {
if (!this.isBrowser) return;
if (this.isDarkMode()) { if (this.isDarkMode()) {
document.body.classList.add('dark-theme'); document.body.classList.add('dark-theme');
} else { } else {

View File

@ -9,6 +9,7 @@ import {
} from '@angular/platform-browser'; } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { provideIntlConfig } from '@mmstack/translate';
import Aura from '@primeng/themes/aura'; import Aura from '@primeng/themes/aura';
import { providePrimeNG } from 'primeng/config'; import { providePrimeNG } from 'primeng/config';
import { routes } from './app.routes'; import { routes } from './app.routes';
@ -25,6 +26,9 @@ export const appConfig: ApplicationConfig = {
preset: Aura, preset: Aura,
}, },
}), }),
provideIntlConfig({
defaultLocale: 'en',
}),
], ],
}; };

View File

@ -1,20 +1,52 @@
import { Routes } from '@angular/router'; import { inject, LOCALE_ID } from '@angular/core';
import { ActivatedRouteSnapshot, Routes } from '@angular/router';
import { resolveAppTranslations } from './translations/app.t';
import { LocaleStore } from './translations/locale.service';
export const routes: Routes = [ export const routes: Routes = [
{ path: '', redirectTo: '/students', pathMatch: 'full' },
{ {
path: 'students', path: ':locale',
loadComponent: () => resolve: {
import('./overview.component').then((m) => m.OverviewComponent), localeId: (route: ActivatedRouteSnapshot) => {
}, const store = inject(LocaleStore);
{
path: 'students/add', const locale = route.params['locale'] || 'en';
loadComponent: () =>
import('./add-student.component').then((m) => m.AddStudentComponent), store.locale.set(locale);
},
{ return locale;
path: 'students/edit', },
loadComponent: () => resolveAppTranslations,
import('./edit-student.component').then((m) => m.EditStudentComponent), },
providers: [
{
provide: LOCALE_ID,
useFactory: (store: LocaleStore) => {
return store.getCurrentLocale();
},
deps: [LocaleStore],
},
],
children: [
{
path: 'students',
loadComponent: () =>
import('./overview.component').then((m) => m.OverviewComponent),
},
{
path: 'students/add',
loadComponent: () =>
import('./add-student.component').then((m) => m.AddStudentComponent),
},
{
path: 'students/edit',
loadComponent: () =>
import('./edit-student.component').then(
(m) => m.EditStudentComponent
),
},
],
}, },
{ path: '', redirectTo: '/en/students', pathMatch: 'full' },
]; ];

View File

@ -1,7 +1,8 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, computed, inject, OnInit } from '@angular/core'; import { Component, computed, inject, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { Router } from '@angular/router';
import { queryParam } from '@mmstack/router-core';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext';
import { MultiSelectModule } from 'primeng/multiselect'; import { MultiSelectModule } from 'primeng/multiselect';
@ -147,13 +148,12 @@ import { StudentService } from './student.service';
}) })
export class EditStudentComponent implements OnInit { export class EditStudentComponent implements OnInit {
private readonly studentService = inject(StudentService); private readonly studentService = inject(StudentService);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly queryId = queryParam('id');
protected student = computed(() => { protected student = computed(() =>
const id = this.route.snapshot.queryParams['id']; this.studentService.getStudentById(this.queryId() ?? '')
return this.studentService.getStudentById(id); );
});
protected courseOptions = computed(() => { protected courseOptions = computed(() => {
return [ return [

View File

@ -4,15 +4,16 @@ import { Router } from '@angular/router';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { TableModule } from 'primeng/table'; import { TableModule } from 'primeng/table';
import { StudentService } from './student.service'; import { StudentService } from './student.service';
import { AppTranslatePipe } from './translations/translate.pipe';
@Component({ @Component({
selector: 'app-overview', selector: 'app-overview',
standalone: true, standalone: true,
imports: [CommonModule, TableModule, ButtonModule], imports: [CommonModule, TableModule, ButtonModule, AppTranslatePipe],
template: ` template: `
<div> <div>
<div class="header-actions"> <div class="header-actions">
<h2 class="page-title">Student Overview</h2> <h2 class="page-title">{{ 'app.studentOverview' | translate }}</h2>
<div class="action-buttons-header"> <div class="action-buttons-header">
<button <button
pButton pButton

View File

@ -0,0 +1,6 @@
import { createAppTranslation } from './app.namespace';
export default createAppTranslation('sl', {
studentOverview: 'Pregled študentov',
});

View File

@ -0,0 +1,12 @@
import { createNamespace } from '@mmstack/translate';
const ns = createNamespace('app', {
studentOverview: 'Student Overview',
});
export default ns.translation;
export type AppLocale = (typeof ns)['translation'];
export const createAppTranslation = ns.createTranslation;

View File

@ -0,0 +1,12 @@
import { registerNamespace } from '@mmstack/translate';
const r = registerNamespace(
() => import('./app.namespace').then((m) => m.default),
{
sl: () => import('./app-sl.translation').then((m) => m.default),
}
);
export const injectAppT = r.injectNamespaceT;
export const resolveAppTranslations = r.resolveNamespaceTranslation;

View File

@ -0,0 +1,34 @@
import { inject, Injectable, signal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class LocaleStore {
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
locale = signal('en');
getCurrentLocale() {
return this.locale();
}
setLocale(locale: string) {
if (this.locale() === locale) return;
this.locale.set(locale);
const urlSegments = this.router.url.split('/');
const routePath = urlSegments.slice(2).join('/');
const newUrl = `/${locale}/${routePath}`;
window.location.href = newUrl;
}
toggleLocale() {
const newLocale = this.locale() === 'en' ? 'sl' : 'en';
this.setLocale(newLocale);
}
}

View File

@ -0,0 +1,9 @@
import { Pipe } from '@angular/core';
import { BaseTranslatePipe } from '@mmstack/translate';
import { type AppLocale } from './app.namespace';
@Pipe({
name: 'translate',
})
export class AppTranslatePipe extends BaseTranslatePipe<AppLocale> {}

View File

@ -1,14 +1,28 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist/out-tsc", "outDir": "./dist",
"types": ["node"] "rootDir": ".",
}, "sourceMap": true,
"include": ["src/**/*.ts"], "declaration": false,
"exclude": [ "moduleResolution": "bundler",
"jest.config.ts", "emitDecoratorMetadata": true,
"src/test-setup.ts", "experimentalDecorators": true,
"src/**/*.test.ts", "esModuleInterop": true,
"src/**/*.spec.ts" "importHelpers": true,
] "target": "ES2022",
"module": "esnext",
"lib": ["esnext", "dom", "esnext.array"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": [
"jest.config.ts",
"src/test-setup.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts"
]
} }