fix(server): resolve zh-TW default language, close lint gap on bracket env access

This commit is contained in:
jubnl
2026-07-25 16:19:15 +02:00
parent f539c2aab7
commit e162da6dbe
6 changed files with 27 additions and 3 deletions
+7
View File
@@ -72,6 +72,13 @@ export default tseslint.config(
message:
'Read configuration via src/app-config (readEnv()/derive/tokens), not process.env. Exemptions: eslint.config.mjs + src/app-config/README.md.',
},
{
// Bracket-notation variant (process['env']) — property is a Literal
// node (.value), so the selector above can't match it.
selector: "MemberExpression[object.name='process'][property.value='env']",
message:
'Read configuration via src/app-config (readEnv()/derive/tokens), not process.env. Exemptions: eslint.config.mjs + src/app-config/README.md.',
},
],
},
},
+5 -1
View File
@@ -54,7 +54,11 @@ export function deriveApp(raw: RawEnv) {
function resolveDefaultLanguage(raw: string | undefined): string {
const lang = raw?.toLowerCase() || 'en';
return SUPPORTED_LANGUAGE_CODES.includes(lang) ? lang : 'en';
// Case-insensitive match resolved to the CANONICAL code (zh-TW, not zh-tw).
// The legacy lowercase-only `includes` could never match zh-TW and silently
// fell back to English for a supported language — deliberate fix.
const canonical = SUPPORTED_LANGUAGE_CODES.find((c) => c.toLowerCase() === lang);
return canonical ?? 'en';
}
export function deriveHttp(raw: RawEnv) {
+2 -1
View File
@@ -62,7 +62,8 @@ export const envSchema = z.object({
APP_VERSION: anyString,
APP_URL: url,
ALLOWED_ORIGINS: anyString,
DEFAULT_LANGUAGE: oneOf([...SUPPORTED_LANGUAGE_CODES]),
// Candidates lowercased so mixed-case codes (zh-TW) validate case-insensitively.
DEFAULT_LANGUAGE: oneOf(SUPPORTED_LANGUAGE_CODES.map((c) => c.toLowerCase())),
// Security / session
ENCRYPTION_KEY: anyString,
+7
View File
@@ -10,6 +10,13 @@
*
* Consumption pattern:
* constructor(@Inject(httpConfig.KEY) private readonly http: ConfigType<typeof httpConfig>) {}
*
* NOTE: today only httpConfig has a DI consumer (bootstrap.ts threads it into
* the pre-init middleware); the migrated call sites otherwise read through
* readEnv() because they live in classes whose unit tests instantiate them
* directly. The full token set is provisioned deliberately — it is the
* sanctioned pattern for NEW Nest consumers of boot-stable config, not dead
* scaffolding to prune.
*/
import { registerAs } from '@nestjs/config';
import {
+5 -1
View File
@@ -39,11 +39,15 @@ describe('deriveApp', () => {
expect(deriveApp({ APP_URL: 'https://x.example/' }).appUrl).toBe('https://x.example/');
});
it('DEFAULT_LANGUAGE resolves: lowercased, validated, en fallback (config.ts semantics)', () => {
it('DEFAULT_LANGUAGE resolves: case-insensitive, canonical code, en fallback', () => {
expect(deriveApp({ DEFAULT_LANGUAGE: 'EN' }).defaultLanguage).toBe('en');
expect(deriveApp({ DEFAULT_LANGUAGE: 'de' }).defaultLanguage).toBe('de');
expect(deriveApp({ DEFAULT_LANGUAGE: 'klingon' }).defaultLanguage).toBe('en');
expect(deriveApp({}).defaultLanguage).toBe('en');
// Mixed-case canonical codes resolve to their canonical form (legacy could
// never match zh-TW and silently served English).
expect(deriveApp({ DEFAULT_LANGUAGE: 'zh-TW' }).defaultLanguage).toBe('zh-TW');
expect(deriveApp({ DEFAULT_LANGUAGE: 'zh-tw' }).defaultLanguage).toBe('zh-TW');
});
});
@@ -89,6 +89,7 @@ describe('validateEnvAtBoot', () => {
['TREK_API_DOCS_ENABLED', ' TRUE '],
['TREK_PLUGINS_ENABLED', ' OFF '],
['DEFAULT_LANGUAGE', 'EN'],
['DEFAULT_LANGUAGE', 'zh-TW'],
['LOG_LEVEL', 'ERROR'],
['SESSION_DURATION', ' 12H '],
])('accepts differently-cased/padded valid %s=%s', (key, value) => {