Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions apps/docs/src/content/docs/en/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,26 @@ The full canonical rules are shipped per language for AI assistants that prefer
| Flag | Description |
| --- | --- |
| `--yes`, `-y` | Accept defaults and overwrite existing files without prompting |
| `--lang <ko\|en>` | Force the CLI locale (defaults to `$LC_ALL` / `$LANG`) |
| `--locale <ko\|en>` | Force the CLI locale (defaults to `$LC_ALL` / `$LANG`) |
| `--help`, `-h` | Print help |
| `--version`, `-v` | Print the CLI version |

## Localisation

The CLI auto-detects your locale from `LC_ALL`, `LANG`, or `LC_MESSAGES` and currently ships Korean and English messages. Override with `--lang ko` or `--lang en`:
The CLI auto-detects your locale from `LC_ALL`, `LANG`, or `LC_MESSAGES` and currently ships Korean and English messages. Override with `--locale ko` or `--locale en`:

```bash
pleaseai-code-style --lang ko init
pleaseai-code-style --locale ko init
```

`--lang ko` and `--lang en` still set the locale for now, with a deprecation warning. When both flags are given, `--locale` wins. Any other `--lang` value, or a `--locale` value other than `ko` or `en`, exits with an error (exit code 1). A future release uses `--lang` to select programming languages.

## CI Usage

Combine `--yes` with a locale override for a fully non-interactive invocation suitable for CI or project templates:

```bash
pleaseai-code-style --yes --lang en init
pleaseai-code-style --yes --locale en init
```

This accepts defaults for every tool, overwrites any existing config files, and uses English output.
Expand Down
10 changes: 6 additions & 4 deletions apps/docs/src/content/docs/ko/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,26 @@ CLI는 두 마커 주석 사이의 내용 **만** 관리하며 — `AGENTS.md`
| 플래그 | 설명 |
| --- | --- |
| `--yes`, `-y` | 기본값을 수락하고 기존 파일을 확인 없이 덮어씀 |
| `--lang <ko\|en>` | CLI 언어 강제 지정 (기본값은 `$LC_ALL` / `$LANG`) |
| `--locale <ko\|en>` | CLI 언어 강제 지정 (기본값은 `$LC_ALL` / `$LANG`) |
| `--help`, `-h` | 도움말 출력 |
| `--version`, `-v` | CLI 버전 출력 |

## 현지화

CLI는 `LC_ALL`, `LANG`, `LC_MESSAGES`에서 locale을 자동 감지하며, 현재 한국어와 영어 메시지를 제공합니다. `--lang ko` 또는 `--lang en`으로 강제 지정할 수 있습니다:
CLI는 `LC_ALL`, `LANG`, `LC_MESSAGES`에서 locale을 자동 감지하며, 현재 한국어와 영어 메시지를 제공합니다. `--locale ko` 또는 `--locale en`으로 강제 지정할 수 있습니다:

```bash
pleaseai-code-style --lang ko init
pleaseai-code-style --locale ko init
```

`--lang ko`와 `--lang en`도 당분간은 로케일을 지정하지만 사용 중단 경고를 출력합니다. 두 플래그를 함께 주면 `--locale`이 우선합니다. 그 밖의 `--lang` 값이나 지원하지 않는 `--locale` 값은 무시되지 않고 오류로 종료합니다(종료 코드 1). 이후 릴리스에서는 `--lang`이 프로그래밍 언어 선택에 쓰입니다.

## CI에서의 사용

`--yes`와 언어 옵션을 조합하면 CI나 프로젝트 템플릿에 적합한 완전 비대화형 실행을 만들 수 있습니다:

```bash
pleaseai-code-style --yes --lang en init
pleaseai-code-style --yes --locale en init
```

모든 도구의 기본값을 수락하고, 기존 설정 파일을 덮어쓰며, 영어로 출력합니다.
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,19 @@ CLI는 아래 마커 사이의 내용만 관리합니다 — `AGENTS.md`의 나
| 플래그 | 설명 |
| --- | --- |
| `--yes`, `-y` | 기본값을 수락하고 기존 파일을 확인 없이 덮어씀 |
| `--lang <ko\|en>` | CLI 언어 강제 지정 (기본값은 `$LANG`) |
| `--locale <ko\|en>` | CLI 언어 강제 지정 (기본값은 `$LANG`) |
| `--help`, `-h` | 도움말 출력 |
| `--version`, `-v` | 버전 출력 |

## 현지화

CLI는 `LC_ALL` / `LANG` / `LC_MESSAGES`에서 locale을 자동 감지하며, 현재 한국어와
영어 메시지를 제공합니다. `--lang ko` 또는 `--lang en`으로 강제 지정할 수 있습니다.
영어 메시지를 제공합니다. `--locale ko` 또는 `--locale en`으로 강제 지정할 수 있습니다.

`--lang ko`와 `--lang en`도 당분간은 로케일을 지정하지만 사용 중단 경고를 출력합니다.
두 플래그를 함께 주면 `--locale`이 우선합니다. 그 밖의 `--lang` 값이나 지원하지 않는
`--locale` 값은 무시되지 않고 오류로 종료합니다(종료 코드 1). 이후 릴리스에서는
`--lang`이 프로그래밍 언어 선택에 쓰입니다.

## 라이선스

Expand Down
11 changes: 8 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,15 +404,20 @@ JavaScript).
| Flag | Description |
| --- | --- |
| `--yes`, `-y` | Accept defaults, overwrite existing files without prompting |
| `--lang <ko\|en>` | Force the CLI locale (defaults to `$LANG`) |
| `--locale <ko\|en>` | Force the CLI locale (defaults to `$LANG`) |
| `--help`, `-h` | Print help |
| `--version`, `-v` | Print version |

## Localisation

The CLI auto-detects your locale from `LC_ALL` / `LANG` / `LC_MESSAGES` and
currently ships Korean and English messages. Override with `--lang ko` or
`--lang en`.
currently ships Korean and English messages. Override with `--locale ko` or
`--locale en`.

`--lang ko` and `--lang en` still set the locale for now, with a deprecation
warning. When both flags are given, `--locale` wins. Any other `--lang` value, or
a `--locale` value other than `ko` or `en`, exits with an error (exit code 1). A
future release uses `--lang` to select programming languages.

## License

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/check/run-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function runCheck(argv: string[], version: () => string): number {
config: { type: 'string' },
help: { type: 'boolean', short: 'h' },
version: { type: 'boolean', short: 'v' },
// Global option, already applied by the entry point.
locale: { type: 'string' },
lang: { type: 'string' },
},
})
Expand Down
54 changes: 52 additions & 2 deletions packages/cli/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
unknownCommand: (cmd: string) => string
unexpectedArguments: (args: string) => string
optionBeforeCheck: (option: string) => string
deprecatedLangFlag: (locale: Locale) => string
ignoredLangFlag: (locale: Locale) => string
invalidLocaleFlag: (flag: LocaleFlag, value: string) => string
usage: string
checkUsage: string
}
Expand Down Expand Up @@ -57,6 +60,11 @@
unknownCommand: cmd => `Unknown command: ${cmd}`,
unexpectedArguments: args => `Unexpected arguments: ${args}`,
optionBeforeCheck: option => `${option} is a check option; put it after \`check\` (please-style check ${option})`,
deprecatedLangFlag: locale => `--lang ${locale} is deprecated; use --locale ${locale}. A future release will use --lang to select programming languages.`,
ignoredLangFlag: locale => `--lang ${locale} is deprecated and ignored because --locale is set; remove it. A future release will use --lang to select programming languages.`,
invalidLocaleFlag: (flag, value) => flag === '--locale'
? `Unknown --locale value: "${value}". Use ko or en.`
: `Unknown --lang value: "${value}". To set the CLI locale, use --locale <ko|en>.`,
usage: `Usage:
pleaseai-code-style <command> [options]

Expand All @@ -68,7 +76,7 @@

Options:
--yes, -y Accept defaults, overwrite existing files
--lang <ko|en> Force CLI locale
--locale <ko|en> Force CLI locale
--help, -h Show this message
--version, -v Print version`,
checkUsage: `Usage:
Expand Down Expand Up @@ -113,6 +121,11 @@
unknownCommand: cmd => `알 수 없는 명령어: ${cmd}`,
unexpectedArguments: args => `예상하지 못한 인자: ${args}`,
optionBeforeCheck: option => `${option}는 check 옵션입니다. \`check\` 뒤에 쓰세요 (please-style check ${option})`,
deprecatedLangFlag: locale => `--lang ${locale} 옵션은 더 이상 권장하지 않습니다. 대신 --locale ${locale} 옵션을 쓰세요. 이후 릴리스에서 --lang은 프로그래밍 언어 선택에 쓰입니다.`,
ignoredLangFlag: locale => `--lang ${locale} 옵션은 더 이상 권장하지 않으며, --locale 옵션이 있어 무시합니다. 이 옵션을 지우세요. 이후 릴리스에서 --lang은 프로그래밍 언어 선택에 쓰입니다.`,
invalidLocaleFlag: (flag, value) => flag === '--locale'
? `알 수 없는 --locale 값: "${value}". ko 또는 en을 쓰세요.`
: `알 수 없는 --lang 값: "${value}". CLI 로케일은 --locale <ko|en>으로 지정하세요.`,
usage: `사용법:
pleaseai-code-style <command> [options]

Expand All @@ -124,7 +137,7 @@

옵션:
--yes, -y 기본값 수락, 기존 파일 덮어쓰기
--lang <ko|en> CLI 로케일 강제 지정
--locale <ko|en> CLI 로케일 강제 지정
--help, -h 이 메시지 표시
--version, -v 버전 출력`,
checkUsage: `사용법:
Expand Down Expand Up @@ -154,6 +167,43 @@
return env.toLowerCase().startsWith('ko') ? 'ko' : 'en'
}

export type LocaleFlag = '--locale' | '--lang'

type FlagValue = string | boolean | undefined

export interface LangWarning {
kind: 'deprecated' | 'ignored'
locale: Locale
}

export type LocaleFlags
= | { kind: 'ok', locale: Locale, langWarning?: LangWarning }
| { kind: 'invalid', locale: Locale, flag: LocaleFlag, value: string }

function asLocale(value: FlagValue): Locale | undefined {
return value === 'ko' || value === 'en' ? value : undefined
}

function flagText(value: FlagValue): string {
return typeof value === 'string' ? value : ''
}

export function resolveLocaleFlags(flags: { locale?: FlagValue, lang?: FlagValue }): LocaleFlags {

Check warning on line 191 in packages/cli/src/i18n.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider removing 'undefined' type or '?' specifier, one of them is redundant.

See more on https://sonarcloud.io/project/issues?id=pleaseai_code-style&issues=AaFoBLHlx5M6XXONooGf&open=AaFoBLHlx5M6XXONooGf&pullRequest=68

Check warning on line 191 in packages/cli/src/i18n.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider removing 'undefined' type or '?' specifier, one of them is redundant.

See more on https://sonarcloud.io/project/issues?id=pleaseai_code-style&issues=AaFoBLHlx5M6XXONooGg&open=AaFoBLHlx5M6XXONooGg&pullRequest=68
const fromLocale = asLocale(flags.locale)
const fromLang = asLocale(flags.lang)
const locale = fromLocale ?? fromLang ?? detectLocale()
if (flags.locale !== undefined && fromLocale === undefined) {
return { kind: 'invalid', locale, flag: '--locale', value: flagText(flags.locale) }
}
if (flags.lang !== undefined && fromLang === undefined) {
return { kind: 'invalid', locale, flag: '--lang', value: flagText(flags.lang) }
}
if (fromLang === undefined) {
return { kind: 'ok', locale }
}
return { kind: 'ok', locale, langWarning: { kind: fromLocale === undefined ? 'deprecated' : 'ignored', locale: fromLang } }
}

let current: Locale = 'en'

export function setLocale(locale: Locale): void {
Expand Down
14 changes: 12 additions & 2 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { fileURLToPath } from 'node:url'
import { parseArgs } from 'node:util'
import { runCheck } from './check/run-check.js'
import { runDoctor, runInit, runUpdate } from './commands.js'
import { detectLocale, setLocale, t } from './i18n.js'
import { resolveLocaleFlags, setLocale, t } from './i18n.js'

const GLOBAL_OPTIONS = {
yes: { type: 'boolean', short: 'y' },
help: { type: 'boolean', short: 'h' },
version: { type: 'boolean', short: 'v' },
locale: { type: 'string' },
lang: { type: 'string' },
} as const

Expand All @@ -23,7 +24,16 @@ const { values: allValues, positionals, tokens } = parseArgs({
options: GLOBAL_OPTIONS,
})

setLocale(detectLocale(typeof allValues.lang === 'string' ? allValues.lang : undefined))
const localeFlags = resolveLocaleFlags({ locale: allValues.locale, lang: allValues.lang })
setLocale(localeFlags.locale)
if (localeFlags.kind === 'invalid') {
process.stderr.write(`${t('invalidLocaleFlag')(localeFlags.flag, localeFlags.value)}\n`)
process.exit(1)
}
else if (localeFlags.langWarning !== undefined) {
const { kind, locale } = localeFlags.langWarning
process.stderr.write(`${t(kind === 'deprecated' ? 'deprecatedLangFlag' : 'ignoredLangFlag')(locale)}\n`)
}

const command = positionals[0] ?? 'init'
// `check` parses its own flags strictly, so only what precedes it is global
Expand Down
25 changes: 24 additions & 1 deletion packages/cli/test/i18n.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import process from 'node:process'
import { afterEach, describe, expect, test } from 'bun:test'
import { detectLocale, setLocale, t } from '../src/i18n.js'
import { detectLocale, resolveLocaleFlags, setLocale, t } from '../src/i18n.js'

describe('detectLocale', () => {
const originalLang = process.env.LANG
Expand Down Expand Up @@ -57,6 +57,29 @@ describe('detectLocale', () => {
})
})

describe('resolveLocaleFlags', () => {
test('--locale sets the locale without a warning', () => {
expect(resolveLocaleFlags({ locale: 'ko' })).toEqual({ kind: 'ok', locale: 'ko' })
})

test('--lang ko|en still sets the locale and is reported as deprecated', () => {
expect(resolveLocaleFlags({ lang: 'en' })).toEqual({ kind: 'ok', locale: 'en', langWarning: { kind: 'deprecated', locale: 'en' } })
})

test('--locale wins over --lang, which is reported as ignored', () => {
expect(resolveLocaleFlags({ locale: 'en', lang: 'ko' })).toEqual({ kind: 'ok', locale: 'en', langWarning: { kind: 'ignored', locale: 'ko' } })
})

test('an unknown --lang value is invalid instead of ignored', () => {
expect(resolveLocaleFlags({ lang: 'dart' })).toMatchObject({ kind: 'invalid', flag: '--lang', value: 'dart' })
expect(resolveLocaleFlags({ lang: true })).toMatchObject({ kind: 'invalid', flag: '--lang', value: '' })
})

test('an unknown --locale value is invalid', () => {
expect(resolveLocaleFlags({ locale: 'fr' })).toMatchObject({ kind: 'invalid', flag: '--locale', value: 'fr' })
})
})

describe('t (translation lookup)', () => {
test('returns English messages when locale is en', () => {
setLocale('en')
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { spawnSync } from 'node:child_process'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { describe, expect, test } from 'bun:test'

const CLI = fileURLToPath(new URL('../src/index.ts', import.meta.url))

function run(args: string[]): { status: number | null, stdout: string, stderr: string } {
const env = { ...process.env, LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8', LC_MESSAGES: 'en_US.UTF-8' }
const res = spawnSync('bun', [CLI, ...args], { encoding: 'utf-8', env })
return { status: res.status, stdout: res.stdout, stderr: res.stderr }
}

describe('locale flags', () => {
test('--locale sets the message language', () => {
const res = run(['--locale', 'ko', '--help'])
expect(res.status).toBe(0)
expect(res.stdout).toContain('사용법:')
expect(res.stderr).toBe('')
})

test('--lang ko warns that it moved to --locale and still sets the locale', () => {
const res = run(['--lang', 'ko', '--help'])
expect(res.status).toBe(0)
expect(res.stdout).toContain('사용법:')
expect(res.stderr).toContain('--locale ko')
})

test('--lang overridden by --locale is reported as ignored, not as the migration target', () => {
const res = run(['--locale', 'en', '--lang', 'ko', '--help'])
expect(res.status).toBe(0)
expect(res.stdout).toContain('Usage:')
expect(res.stderr).toContain('--lang ko is deprecated and ignored because --locale is set; remove it.')
})

test('an unknown --lang value exits non-zero instead of being ignored', () => {
const res = run(['--lang', 'dart', '--help'])
expect(res.status).toBe(1)
expect(res.stdout).toBe('')
expect(res.stderr).toContain('Unknown --lang value: "dart"')
})
})
Loading