mirror of
https://github.com/tiennm99/dating.git
synced 2026-08-31 02:25:24 +00:00
refactor: convert typescript sources to javascript with jsdoc
- rewrite lib, routes, and vite config as js with jsdoc type annotations - replace tsconfig with jsconfig and point svelte-check at it - drop typescript-eslint from eslint config and dev dependencies - resolve nav hrefs in template to satisfy svelte navigation lint rule - add gitattributes forcing lf so prettier and autocrlf stop conflicting
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
*.png binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
@@ -21,7 +21,7 @@ Warm-witty dating JD and CV site for Tiến Nguyễn Minh.
|
||||
|
||||
- SvelteKit 2
|
||||
- Svelte 5
|
||||
- TypeScript
|
||||
- JavaScript with JSDoc type checking
|
||||
- `@sveltejs/adapter-static`
|
||||
- GitHub Pages
|
||||
|
||||
|
||||
+1
-18
@@ -4,34 +4,17 @@ import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig, includeIgnoreFile } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
|
||||
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
ts.configs.recommended,
|
||||
svelte.configs.recommended,
|
||||
prettier,
|
||||
svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } }
|
||||
},
|
||||
{
|
||||
// Override or add rule settings here, such as:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
+2
-3
@@ -10,8 +10,8 @@
|
||||
"build:gh": "BUILD_PROFILE=gh vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
@@ -30,7 +30,6 @@
|
||||
"svelte": "^5.56.1",
|
||||
"svelte-check": "^4.6.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.60.1",
|
||||
"vite": "^8.0.16"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2
-182
@@ -53,9 +53,6 @@ importers:
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.60.1
|
||||
version: 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
vite:
|
||||
specifier: ^8.0.16
|
||||
version: 8.0.16(@types/node@24.13.2)
|
||||
@@ -317,65 +314,10 @@ packages:
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.61.1':
|
||||
resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.61.1
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/parser@8.61.1':
|
||||
resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/project-service@8.61.1':
|
||||
resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/scope-manager@8.61.1':
|
||||
resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.61.1':
|
||||
resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/type-utils@8.61.1':
|
||||
resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/types@8.61.1':
|
||||
resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.61.1':
|
||||
resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/utils@8.61.1':
|
||||
resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.61.1':
|
||||
resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -588,10 +530,6 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
ignore@7.0.5:
|
||||
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
imurmurhash@0.1.4:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
@@ -893,12 +831,6 @@ packages:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ts-api-utils@2.5.0:
|
||||
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
|
||||
engines: {node: '>=18.12'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4'
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -906,13 +838,6 @@ packages:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
typescript-eslint@8.61.1:
|
||||
resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
typescript@6.0.3:
|
||||
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -1207,96 +1132,8 @@ snapshots:
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/scope-manager': 8.61.1
|
||||
'@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/visitor-keys': 8.61.1
|
||||
eslint: 10.5.0
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.61.1
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/visitor-keys': 8.61.1
|
||||
debug: 4.4.3
|
||||
eslint: 10.5.0
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.61.1(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
debug: 4.4.3
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/scope-manager@8.61.1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
'@typescript-eslint/visitor-keys': 8.61.1
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)':
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
debug: 4.4.3
|
||||
eslint: 10.5.0
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/types@8.61.1': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
'@typescript-eslint/visitor-keys': 8.61.1
|
||||
debug: 4.4.3
|
||||
minimatch: 10.2.5
|
||||
semver: 7.8.5
|
||||
tinyglobby: 0.2.17
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0)
|
||||
'@typescript-eslint/scope-manager': 8.61.1
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||
eslint: 10.5.0
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.61.1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.61.1
|
||||
eslint-visitor-keys: 5.0.1
|
||||
'@typescript-eslint/types@8.61.1':
|
||||
optional: true
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||
dependencies:
|
||||
@@ -1497,8 +1334,6 @@ snapshots:
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
@@ -1769,10 +1604,6 @@ snapshots:
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
ts-api-utils@2.5.0(typescript@6.0.3):
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
|
||||
@@ -1780,17 +1611,6 @@ snapshots:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
typescript-eslint@8.61.1(eslint@10.5.0)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||
eslint: 10.5.0
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
typescript@6.0.3: {}
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { SiteCopy } from './types';
|
||||
|
||||
export const en: SiteCopy = {
|
||||
/** @type {import('./types.js').SiteCopy} */
|
||||
export const en = {
|
||||
layout: {
|
||||
skipLink: 'Skip to content',
|
||||
brand: 'Dating JD & CV',
|
||||
@@ -119,7 +118,10 @@ export const en: SiteCopy = {
|
||||
['Favorite team', 'T1 — Keria is the bias'],
|
||||
['Lifestyle', 'Homebody, computer enthusiast, and careful spender'],
|
||||
['Pets', 'Not really a pet person'],
|
||||
['Spiritual side', 'Year of the Cat 🐱; compatible with Monkey 🐒, Tiger 🐯, Snake 🐍, Horse 🐴, Pig 🐷, Dragon 🐉, and Dog 🐶'],
|
||||
[
|
||||
'Spiritual side',
|
||||
'Year of the Cat 🐱; compatible with Monkey 🐒, Tiger 🐯, Snake 🐍, Horse 🐴, Pig 🐷, Dragon 🐉, and Dog 🐶'
|
||||
],
|
||||
['Private note', 'A little naughty, with a particular fondness for beautiful feet']
|
||||
],
|
||||
closingEyebrow: 'Closing statement',
|
||||
@@ -0,0 +1,39 @@
|
||||
import { en } from './en.js';
|
||||
import { vi } from './vi.js';
|
||||
import { localeOptions, themeOptions } from './types.js';
|
||||
|
||||
export * from './types.js';
|
||||
|
||||
/** @typedef {import('./types.js').Locale} Locale */
|
||||
/** @typedef {import('./types.js').Theme} Theme */
|
||||
/** @typedef {import('./types.js').CopyFact} CopyFact */
|
||||
/** @typedef {import('./types.js').CopyStrength} CopyStrength */
|
||||
/** @typedef {import('./types.js').SiteCopy} SiteCopy */
|
||||
|
||||
/** @type {Locale} */
|
||||
export const defaultLocale = 'vi';
|
||||
|
||||
/** @type {Theme} */
|
||||
export const defaultTheme = 'light';
|
||||
|
||||
/** @satisfies {Record<Locale, SiteCopy>} */
|
||||
export const messages = {
|
||||
en,
|
||||
vi
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string | null} value
|
||||
* @returns {value is Locale}
|
||||
*/
|
||||
export function isLocale(value) {
|
||||
return localeOptions.includes(/** @type {Locale} */ (value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | null} value
|
||||
* @returns {value is Theme}
|
||||
*/
|
||||
export function isTheme(value) {
|
||||
return themeOptions.includes(/** @type {Theme} */ (value));
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { en } from './en';
|
||||
import { vi } from './vi';
|
||||
import { localeOptions, themeOptions, type Locale, type SiteCopy, type Theme } from './types';
|
||||
|
||||
export * from './types';
|
||||
|
||||
export const defaultLocale: Locale = 'vi';
|
||||
export const defaultTheme: Theme = 'light';
|
||||
|
||||
export const messages = {
|
||||
en,
|
||||
vi
|
||||
} satisfies Record<Locale, SiteCopy>;
|
||||
|
||||
export function isLocale(value: string | null): value is Locale {
|
||||
return localeOptions.includes(value as Locale);
|
||||
}
|
||||
|
||||
export function isTheme(value: string | null): value is Theme {
|
||||
return themeOptions.includes(value as Theme);
|
||||
}
|
||||
@@ -7,23 +7,29 @@ import {
|
||||
isTheme,
|
||||
localeOptions,
|
||||
messages,
|
||||
themeOptions,
|
||||
type Locale,
|
||||
type Theme
|
||||
} from './index';
|
||||
themeOptions
|
||||
} from './index.js';
|
||||
|
||||
/** @typedef {import('./types.js').Locale} Locale */
|
||||
/** @typedef {import('./types.js').Theme} Theme */
|
||||
|
||||
const localeStorageKey = 'dating-locale';
|
||||
const themeStorageKey = 'dating-theme';
|
||||
|
||||
export { localeOptions, themeOptions };
|
||||
|
||||
export const locale = writable<Locale>(defaultLocale);
|
||||
export const theme = writable<Theme>(defaultTheme);
|
||||
/** @type {import('svelte/store').Writable<Locale>} */
|
||||
export const locale = writable(defaultLocale);
|
||||
|
||||
/** @type {import('svelte/store').Writable<Theme>} */
|
||||
export const theme = writable(defaultTheme);
|
||||
|
||||
export const copy = derived(locale, ($locale) => messages[$locale]);
|
||||
|
||||
let preferencesStarted = false;
|
||||
|
||||
function readStoredLocale(): Locale {
|
||||
/** @returns {Locale} */
|
||||
function readStoredLocale() {
|
||||
if (!browser) return defaultLocale;
|
||||
|
||||
const storedLocale = readStorage(localeStorageKey);
|
||||
@@ -32,14 +38,16 @@ function readStoredLocale(): Locale {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
function readStoredTheme(): Theme {
|
||||
/** @returns {Theme} */
|
||||
function readStoredTheme() {
|
||||
if (!browser) return defaultTheme;
|
||||
|
||||
const storedTheme = readStorage(themeStorageKey);
|
||||
return isTheme(storedTheme) ? storedTheme : defaultTheme;
|
||||
}
|
||||
|
||||
function readStorage(key: string) {
|
||||
/** @param {string} key */
|
||||
function readStorage(key) {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch {
|
||||
@@ -47,7 +55,11 @@ function readStorage(key: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeStorage(key: string, value: string) {
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
function writeStorage(key, value) {
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch {
|
||||
@@ -55,12 +67,14 @@ function writeStorage(key: string, value: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function applyLocale(nextLocale: Locale) {
|
||||
/** @param {Locale} nextLocale */
|
||||
function applyLocale(nextLocale) {
|
||||
document.documentElement.lang = nextLocale;
|
||||
writeStorage(localeStorageKey, nextLocale);
|
||||
}
|
||||
|
||||
function applyTheme(nextTheme: Theme) {
|
||||
/** @param {Theme} nextTheme */
|
||||
function applyTheme(nextTheme) {
|
||||
document.documentElement.dataset.theme = nextTheme;
|
||||
document.documentElement.style.colorScheme = nextTheme;
|
||||
writeStorage(themeStorageKey, nextTheme);
|
||||
@@ -77,10 +91,12 @@ export function initializePreferences() {
|
||||
theme.subscribe(applyTheme);
|
||||
}
|
||||
|
||||
export function setLocale(nextLocale: Locale) {
|
||||
/** @param {Locale} nextLocale */
|
||||
export function setLocale(nextLocale) {
|
||||
locale.set(nextLocale);
|
||||
}
|
||||
|
||||
export function setTheme(nextTheme: Theme) {
|
||||
/** @param {Theme} nextTheme */
|
||||
export function setTheme(nextTheme) {
|
||||
theme.set(nextTheme);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
export const localeOptions = /** @type {const} */ (['vi', 'en']);
|
||||
/** @typedef {(typeof localeOptions)[number]} Locale */
|
||||
|
||||
export const themeOptions = /** @type {const} */ (['light', 'dark']);
|
||||
/** @typedef {(typeof themeOptions)[number]} Theme */
|
||||
|
||||
/** @typedef {readonly [label: string, value: string]} CopyFact */
|
||||
|
||||
/**
|
||||
* @typedef {object} CopyStrength
|
||||
* @property {string} title
|
||||
* @property {string} body
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} SiteCopy
|
||||
* @property {{
|
||||
* skipLink: string,
|
||||
* brand: string,
|
||||
* homeAria: string,
|
||||
* primaryNavAria: string
|
||||
* }} layout
|
||||
* @property {Record<'home' | 'jd' | 'cv', string>} nav
|
||||
* @property {{
|
||||
* themeGroup: string,
|
||||
* languageGroup: string,
|
||||
* themeActions: Record<Theme, string>,
|
||||
* languageActions: Record<Locale, string>
|
||||
* }} controls
|
||||
* @property {{
|
||||
* metaTitle: string,
|
||||
* metaDescription: string,
|
||||
* heroEyebrow: string,
|
||||
* title: string,
|
||||
* lead: string,
|
||||
* primaryCta: string,
|
||||
* secondaryCta: string,
|
||||
* packetEyebrow: string,
|
||||
* packetTitle: string,
|
||||
* futureLoverTitle: string,
|
||||
* futureLoverBody: string,
|
||||
* committeeTitle: string,
|
||||
* committeeBody: string
|
||||
* }} home
|
||||
* @property {{
|
||||
* metaTitle: string,
|
||||
* metaDescription: string,
|
||||
* heroEyebrow: string,
|
||||
* title: string,
|
||||
* lead: string,
|
||||
* summaryAria: string,
|
||||
* summary: CopyFact[],
|
||||
* benefitsEyebrow: string,
|
||||
* benefitsTitle: string,
|
||||
* benefits: string[],
|
||||
* responsibilitiesEyebrow: string,
|
||||
* responsibilitiesTitle: string,
|
||||
* responsibilities: string[],
|
||||
* niceEyebrow: string,
|
||||
* processTitle: string,
|
||||
* niceToHave: string[],
|
||||
* selectionTitle: string,
|
||||
* selectionBody: string
|
||||
* }} jd
|
||||
* @property {{
|
||||
* metaTitle: string,
|
||||
* metaDescription: string,
|
||||
* heroEyebrow: string,
|
||||
* title: string,
|
||||
* lead: string,
|
||||
* summaryAria: string,
|
||||
* strengthsEyebrow: string,
|
||||
* strengthsTitle: string,
|
||||
* strengths: CopyStrength[],
|
||||
* factsEyebrow: string,
|
||||
* factsTitle: string,
|
||||
* facts: CopyFact[],
|
||||
* closingEyebrow: string,
|
||||
* closingTitle: string,
|
||||
* closingBody: string
|
||||
* }} cv
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -1,80 +0,0 @@
|
||||
export const localeOptions = ['vi', 'en'] as const;
|
||||
export type Locale = (typeof localeOptions)[number];
|
||||
|
||||
export const themeOptions = ['light', 'dark'] as const;
|
||||
export type Theme = (typeof themeOptions)[number];
|
||||
|
||||
export type CopyFact = readonly [label: string, value: string];
|
||||
|
||||
export type CopyStrength = {
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export type SiteCopy = {
|
||||
layout: {
|
||||
skipLink: string;
|
||||
brand: string;
|
||||
homeAria: string;
|
||||
primaryNavAria: string;
|
||||
};
|
||||
nav: Record<'home' | 'jd' | 'cv', string>;
|
||||
controls: {
|
||||
themeGroup: string;
|
||||
languageGroup: string;
|
||||
themeActions: Record<Theme, string>;
|
||||
languageActions: Record<Locale, string>;
|
||||
};
|
||||
home: {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
heroEyebrow: string;
|
||||
title: string;
|
||||
lead: string;
|
||||
primaryCta: string;
|
||||
secondaryCta: string;
|
||||
packetEyebrow: string;
|
||||
packetTitle: string;
|
||||
futureLoverTitle: string;
|
||||
futureLoverBody: string;
|
||||
committeeTitle: string;
|
||||
committeeBody: string;
|
||||
};
|
||||
jd: {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
heroEyebrow: string;
|
||||
title: string;
|
||||
lead: string;
|
||||
summaryAria: string;
|
||||
summary: CopyFact[];
|
||||
benefitsEyebrow: string;
|
||||
benefitsTitle: string;
|
||||
benefits: string[];
|
||||
responsibilitiesEyebrow: string;
|
||||
responsibilitiesTitle: string;
|
||||
responsibilities: string[];
|
||||
niceEyebrow: string;
|
||||
processTitle: string;
|
||||
niceToHave: string[];
|
||||
selectionTitle: string;
|
||||
selectionBody: string;
|
||||
};
|
||||
cv: {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
heroEyebrow: string;
|
||||
title: string;
|
||||
lead: string;
|
||||
summaryAria: string;
|
||||
strengthsEyebrow: string;
|
||||
strengthsTitle: string;
|
||||
strengths: CopyStrength[];
|
||||
factsEyebrow: string;
|
||||
factsTitle: string;
|
||||
facts: CopyFact[];
|
||||
closingEyebrow: string;
|
||||
closingTitle: string;
|
||||
closingBody: string;
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { SiteCopy } from './types';
|
||||
|
||||
export const vi: SiteCopy = {
|
||||
/** @type {import('./types.js').SiteCopy} */
|
||||
export const vi = {
|
||||
layout: {
|
||||
skipLink: 'Bỏ qua đến nội dung',
|
||||
brand: 'Mô tả & hồ sơ hẹn hò',
|
||||
@@ -118,7 +117,10 @@ export const vi: SiteCopy = {
|
||||
['Đội tuyển yêu thích', 'T1 — bias Keria'],
|
||||
['Lối sống', 'Ở nhà, mê máy tính và chi tiêu tiết kiệm'],
|
||||
['Thú cưng', 'Không hợp lắm'],
|
||||
['Tâm linh', 'Tuổi Mão 🐱; hợp tuổi Thân 🐒, Dần 🐯, Tỵ 🐍, Ngọ 🐴, Hợi 🐷, Thìn 🐉 và Tuất 🐶'],
|
||||
[
|
||||
'Tâm linh',
|
||||
'Tuổi Mão 🐱; hợp tuổi Thân 🐒, Dần 🐯, Tỵ 🐍, Ngọ 🐴, Hợi 🐷, Thìn 🐉 và Tuất 🐶'
|
||||
],
|
||||
['Góc riêng tư', 'Hơi tà răm; có thiện cảm đặc biệt với đôi chân đẹp']
|
||||
],
|
||||
closingEyebrow: 'Lời kết',
|
||||
+12
-12
@@ -1,4 +1,4 @@
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
@@ -17,21 +17,21 @@
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
const navItems = [
|
||||
{ href: resolve('/'), labelKey: 'home', routeId: '/' },
|
||||
{ href: resolve('/jd/'), labelKey: 'jd', routeId: '/jd' },
|
||||
{ href: resolve('/cv/'), labelKey: 'cv', routeId: '/cv' }
|
||||
] as const;
|
||||
const navItems = /** @type {const} */ ([
|
||||
{ path: '/', labelKey: 'home', routeId: '/' },
|
||||
{ path: '/jd/', labelKey: 'jd', routeId: '/jd' },
|
||||
{ path: '/cv/', labelKey: 'cv', routeId: '/cv' }
|
||||
]);
|
||||
|
||||
const themeIcons = {
|
||||
const themeIcons = /** @type {const} */ ({
|
||||
light: '☀️',
|
||||
dark: '🌙'
|
||||
} as const;
|
||||
});
|
||||
|
||||
const localeLabels = {
|
||||
const localeLabels = /** @type {const} */ ({
|
||||
vi: 'VI',
|
||||
en: 'EN'
|
||||
} as const;
|
||||
});
|
||||
|
||||
onMount(initializePreferences);
|
||||
</script>
|
||||
@@ -64,9 +64,9 @@
|
||||
|
||||
<div class="header-actions">
|
||||
<nav aria-label={$copy.layout.primaryNavAria}>
|
||||
{#each navItems as item (item.href)}
|
||||
{#each navItems as item (item.path)}
|
||||
<a
|
||||
href={item.href}
|
||||
href={resolve(item.path)}
|
||||
class:active={page.route.id === item.routeId}
|
||||
aria-current={page.route.id === item.routeId ? 'page' : undefined}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import { resolve } from '$app/paths';
|
||||
import heroImage from '$lib/assets/generated/dating-application-hero-background.webp';
|
||||
import { copy } from '$lib/i18n/preferences';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import { copy } from '$lib/i18n/preferences';
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import { copy } from '$lib/i18n/preferences';
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ import adapter from '@sveltejs/adapter-static';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
function resolveBase(): '' | `/${string}` {
|
||||
/** @returns {'' | `/${string}`} */
|
||||
function resolveBase() {
|
||||
const configuredBase =
|
||||
process.env.BASE_PATH ?? (process.env.BUILD_PROFILE === 'gh' ? '/dating' : '');
|
||||
|
||||
if (configuredBase === '' || configuredBase.startsWith('/')) {
|
||||
return configuredBase as '' | `/${string}`;
|
||||
return /** @type {'' | `/${string}`} */ (configuredBase);
|
||||
}
|
||||
|
||||
throw new Error('BASE_PATH must be empty or start with "/"');
|
||||
Reference in New Issue
Block a user