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:
2026-07-23 20:25:41 +07:00
parent 286a689a86
commit 546e8d56f2
20 changed files with 193 additions and 345 deletions
+4
View File
@@ -0,0 +1,4 @@
* text=auto eol=lf
*.png binary
*.webp binary
*.ico binary
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -1,7 +1,6 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
+2 -3
View File
@@ -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"
}
}
+2 -182
View File
@@ -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: {}
+6 -4
View File
@@ -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',
+39
View File
@@ -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));
}
-21
View File
@@ -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);
}
+84
View File
@@ -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 {};
-80
View File
@@ -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;
};
};
+6 -4
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
<script lang="ts">
<script>
import { copy } from '$lib/i18n/preferences';
</script>
+1 -1
View File
@@ -1,4 +1,4 @@
<script lang="ts">
<script>
import { copy } from '$lib/i18n/preferences';
</script>
+3 -2
View File
@@ -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 "/"');