Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
- name: Verify action pins
run: bash scripts/verify-action-pins.sh

- name: Verify sources are English-only

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: Verify sources are English-only
- name: Verify sources contain no unapproved CJK text

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite accurate here, but fine.

run: go run scripts/verify-cjk.go

- name: Check formatting
run: |
unformatted="$(gofmt -s -l .)"
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ open-code-review (`ocr`) is an AI-powered code review CLI tool written in Go (mo
## Code Style

- After writing code, run `make check` to format and check the code.
- `make check` runs: license check, `go mod tidy`, `gofmt -s -w .`, and `go vet`.
- `make check` runs: license check, CJK check, `go mod tidy`, `gofmt -s -w .`, and `go vet`.
- Source files are English-only — comments, identifiers and strings alike. Translated prose belongs in `README.<locale>.md`, `pages/src/content/docs/<locale>/` or an i18n table. `make cjk-check` enforces this in CI; it also rejects fullwidth punctuation (`:`, `(`), which is easy to leave behind in an otherwise English sentence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Source files are English-only — comments, identifiers and strings alike. Translated prose belongs in `README.<locale>.md`, `pages/src/content/docs/<locale>/` or an i18n table. `make cjk-check` enforces this in CI; it also rejects fullwidth punctuation (``, ``), which is easy to leave behind in an otherwise English sentence.
- Source files must not contain unapproved CJK text in comments, identifiers or strings. Translated prose belongs in `README.<locale>.md`, `pages/src/content/docs/<locale>/` or an i18n table. `make cjk-check` enforces this in CI; it also rejects fullwidth punctuation (``, ``), which is easy to leave behind in an otherwise English sentence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is as same as ci.yml:L35

- When CJK text is intentional (an encoding fixture, a language-switcher label), append an `allow-cjk: <reason>` marker comment to that line rather than widening the allowlist in `scripts/verify-cjk.go`.

## Testing

Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
build-all dist sha256sum version-info \
build-linux-amd64 build-linux-arm64 build-darwin-amd64 build-darwin-arm64 \
build-windows-amd64 build-windows-arm64 \
license-check license-add
license-check license-add cjk-check

BINARY_NAME := opencodereview
GO := go
Expand Down Expand Up @@ -64,7 +64,7 @@ fmt:
vet:
LC_ALL=C $(GO) vet $(PACKAGES)

check: license-check
check: license-check cjk-check
$(GO) mod tidy
gofmt -s -w .
LC_ALL=C $(GO) vet $(PACKAGES)
Expand All @@ -73,6 +73,9 @@ check: license-check
license-check:
@bash scripts/verify-license.sh

cjk-check:
@$(GO) run scripts/verify-cjk.go

license-add:
@bash scripts/add-license.sh

Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ inputs:
language:
description: >-
Review output language, written via `ocr config set language`
(e.g. English, 中文). No env var exists for this.
(e.g. English, Chinese). No env var exists for this.
required: false
default: 'English'
llm_timeout:
Expand Down
2 changes: 1 addition & 1 deletion cmd/opencodereview/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestSanitizeTerminal(t *testing.T) {
{"strips carriage return", "fake\rreal", "fakereal"},
{"empty string", "", ""},
{"only control chars", "\x1b\x07\x00\x7f", ""},
{"unicode preserved", "代码审查 レビュー 🔍", "代码审查 レビュー 🔍"},
{"unicode preserved", "代码审查 レビュー 🔍", "代码审查 レビュー 🔍"}, // allow-cjk: fixture asserts non-ASCII output is preserved verbatim
{"mixed safe and unsafe", "path\x1b[0m/file.go", "path[0m/file.go"},
{"strips C1 CSI (U+009B)", "before\u009bafter", "beforeafter"},
{"strips C1 OSC (U+009D)", "before\u009dafter", "beforeafter"},
Expand Down
4 changes: 2 additions & 2 deletions cmd/opencodereview/session_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,11 @@ func TestRunSessionShow_MissingID(t *testing.T) {
}

func TestTruncateUnicode(t *testing.T) {
got := truncate("错误原因:超过限制", 6)
got := truncate("错误原因:超过限制", 6) // allow-cjk: fixture exercises rune-boundary truncation
if !strings.HasSuffix(got, "…") {
t.Fatalf("expected ellipsis suffix, got %q", got)
}
if !strings.Contains(got, "错误") {
if !strings.Contains(got, "错误") { // allow-cjk: fixture exercises rune-boundary truncation
t.Fatalf("expected valid truncated unicode text, got %q", got)
}
}
Expand Down
12 changes: 6 additions & 6 deletions examples/gerrit_ci/post_review_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ def test_suggestion_without_existing(self):
self.assertNotIn("**Suggestion:**", msg)

def test_unicode_comment_roundtrip(self):
content = "空指针解引用:y 可能为 nil"
ri = build([comment(path="pkg/服务.go", content=content)])
self.assertIn("pkg/服务.go", ri["comments"])
self.assertIn(content, entry_of(ri, "pkg/服务.go")["message"])
content = "空指针解引用:y 可能为 nil" # allow-cjk: fixture exercises UTF-8 comment bodies
ri = build([comment(path="pkg/服务.go", content=content)]) # allow-cjk: fixture exercises UTF-8 file paths
self.assertIn("pkg/服务.go", ri["comments"]) # allow-cjk: fixture exercises UTF-8 file paths
self.assertIn(content, entry_of(ri, "pkg/服务.go")["message"]) # allow-cjk: fixture exercises UTF-8 file paths
self.assertEqual(json.loads(json.dumps(ri, ensure_ascii=False)), ri)

def test_path_with_spaces(self):
Expand Down Expand Up @@ -539,15 +539,15 @@ def fake_urlopen(req, timeout=None):
def test_preemptive_basic_auth_and_utf8_body(self):
import base64

req, _parsed = self.post({"message": "空指针解引用:y 可能为 nil"})
req, _parsed = self.post({"message": "空指针解引用:y 可能为 nil"}) # allow-cjk: fixture exercises UTF-8 request payloads
auth = req.get_header("Authorization")
self.assertIsNotNone(auth, "Authorization header must be set preemptively")
self.assertTrue(auth.startswith("Basic "))
self.assertEqual(
base64.b64decode(auth[len("Basic "):]).decode("utf-8"),
"review-bot:s3cret-pass",
)
self.assertIn("空指针解引用".encode("utf-8"), req.data)
self.assertIn("空指针解引用".encode("utf-8"), req.data) # allow-cjk: fixture exercises UTF-8 request payloads
self.assertIn("application/json", req.get_header("Content-type"))

def test_xssi_response_parses(self):
Expand Down
2 changes: 1 addition & 1 deletion internal/config/toolsconfig/tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"main_task": true,
"definition": {
"name": "file_read",
"description": "Use this tool to read file content when you need to get context for git diff. You can specify start_line and end_line to view specific parts of the file.\n\n**Line Range Strategy:**\n- Git diff hunk header provides guidance on how to get more relevant context.\n- Git diff hunk header \"@@-x,y +m,n@@\" indicates that the old file has y lines starting from line x, and the new file has n lines starting from line m.\n- For example, when you need to read 50 lines above and below the current changed code block in the new file, set start_line = m - 50, end_line = m + n + 50.\n\n**Example output:**\nFile:path/to/example.go (Total lines: 50)\nIS_TRUNCATED: false\nLINE_RANGE: 10-12\n// The following is the original content of the file\nfunc main() {\n fmt.Println(\"Hello, World!\")\n}\n\n**Limitations:**\n- If the specified range exceeds 500 lines, only 500 lines will be returned with a truncation notice.\n- This tool can only read file content from the modified version (after changes) in git diff.",
"description": "Use this tool to read file content when you need to get context for git diff. You can specify start_line and end_line to view specific parts of the file.\n\n**Line Range Strategy:**\n- Git diff hunk header provides guidance on how to get more relevant context.\n- Git diff hunk header \"@@-x,y +m,n@@\" indicates that the old file has y lines starting from line x, and the new file has n lines starting from line m.\n- For example, when you need to read 50 lines above and below the current changed code block in the new file, set start_line = m - 50, end_line = m + n + 50.\n\n**Example output:**\nFile: path/to/example.go (Total lines: 50)\nIS_TRUNCATED: false\nLINE_RANGE: 10-12\n// The following is the original content of the file\nfunc main() {\n fmt.Println(\"Hello, World!\")\n}\n\n**Limitations:**\n- If the specified range exceeds 500 lines, only 500 lines will be returned with a truncation notice.\n- This tool can only read file content from the modified version (after changes) in git diff.",
"parameters": {
"type": "object",
"properties": {
Expand Down
4 changes: 2 additions & 2 deletions internal/diff/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func initRepoWithNonASCIIChange(t *testing.T) (string, string) {
runGitTest(t, repo, "config", "commit.gpgsign", "false")
runGitTest(t, repo, "config", "core.quotepath", "true")

relPath := "src/café/(authenticated)/文件.ts"
relPath := "src/café/(authenticated)/文件.ts" // allow-cjk: fixture exercises non-ASCII paths
file := filepath.Join(repo, filepath.FromSlash(relPath))
if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
t.Fatalf("create non-ASCII path: %v", err)
Expand Down Expand Up @@ -153,7 +153,7 @@ func TestWorkspaceDiffPreservesNonASCIIUntrackedPath(t *testing.T) {
repo, trackedPath := initRepoWithNonASCIIChange(t)
runGitTest(t, repo, "checkout", "--", trackedPath)

untrackedPath := "src/café/(authenticated)/新增.ts"
untrackedPath := "src/café/(authenticated)/新增.ts" // allow-cjk: fixture exercises non-ASCII paths
if err := os.WriteFile(filepath.Join(repo, filepath.FromSlash(untrackedPath)), []byte("untracked\n"), 0o644); err != nil {
t.Fatalf("write non-ASCII untracked file: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/session/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ func TestSanitizeReasonTruncatesAndSingleLine(t *testing.T) {
t.Fatal("newlines not collapsed")
}
// Multibyte input must not be cut mid-rune.
multibyte := strings.Repeat("世", maxReasonLen+50)
multibyte := strings.Repeat("世", maxReasonLen+50) // allow-cjk: fixture exercises multibyte truncation
if !utf8.ValidString(sanitizeReason(multibyte)) {
t.Fatal("truncation produced invalid UTF-8")
}
Expand Down
4 changes: 2 additions & 2 deletions internal/viewer/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func TestTruncateText(t *testing.T) {
{"truncated with ellipsis", 3, "hello", "hel…"},
{"empty string", 5, "", ""},
{"n=0 always truncates non-empty", 0, "hi", "…"},
{"unicode shorter than n bytes", 20, "你好世界", "你好世界"},
{"unicode truncated at byte boundary", 6, "你好世界", "你好…"},
{"unicode shorter than n bytes", 20, "你好世界", "你好世界"}, // allow-cjk: fixture exercises rune-boundary truncation
{"unicode truncated at byte boundary", 6, "你好世界", "你好…"}, // allow-cjk: fixture exercises rune-boundary truncation
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions pages/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type { Language } from '../i18n/types';

const LANG_OPTIONS: { value: Language; label: string }[] = [
{ value: 'en', label: 'English' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日本語' },
{ value: 'zh', label: '中文' }, // allow-cjk: language options are labelled in their own language
{ value: 'ja', label: '日本語' }, // allow-cjk: language options are labelled in their own language
{ value: 'ru', label: 'Русский' },
];

Expand Down
2 changes: 1 addition & 1 deletion pages/src/components/HeroSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const terminalLines = [
{ num: 9, content: <span>&nbsp;</span> },
{ num: 10, content: <span style={{ color: TC.dim }}>─── internal/auth/login.go:42-45 ───</span> },
{ num: 11, content: <span style={{ color: TC.text }}>Consider using bcrypt cost factor ≥ 12 for password hashing.</span> },
{ num: 12, content: <span className="terminal-cursor" style={{ color: TC.text }}>|</span> },
{ num: 12, content: <span className="terminal-cursor" style={{ color: TC.text }}>|</span> }, // allow-cjk: fullwidth bar renders the terminal cursor
];

const INSTALL_CHANNELS = [
Expand Down
8 changes: 4 additions & 4 deletions pages/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import type { Language } from '../i18n/types';

const LANG_OPTIONS: { value: Language; label: string }[] = [
{ value: 'en', label: 'English' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日本語' },
{ value: 'zh', label: '中文' }, // allow-cjk: language options are labelled in their own language
{ value: 'ja', label: '日本語' }, // allow-cjk: language options are labelled in their own language
{ value: 'ru', label: 'Русский' },
];

const LANG_BADGE: Record<Language, string> = {
en: 'En',
zh: '中',
ja: 'あ',
zh: '中', // allow-cjk: single-glyph locale badge
ja: 'あ', // allow-cjk: single-glyph locale badge
ru: 'Ru',
};

Expand Down
4 changes: 2 additions & 2 deletions scripts/github-actions/check-translation-sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ function testIdenticalStructurePasses() {
// Same outline, DIFFERENT heading text (simulating translations). Must pass:
// the check compares structure, not text.
const en = readme(["## What is it?", "### Details", "## Usage"]);
const zh = readme(["## 这是什么?", "### 细节", "## 使用方法"]);
const ja = readme(["## これは何ですか?", "### 詳細", "## 使い方"]);
const zh = readme(["## 这是什么?", "### 细节", "## 使用方法"]); // allow-cjk: fixture mimics translated README headings
const ja = readme(["## これは何ですか?", "### 詳細", "## 使い方"]); // allow-cjk: fixture mimics translated README headings
const { ok, errors } = compareReadmeStructures([
{ name: "README.md", content: en },
{ name: "README.zh-CN.md", content: zh },
Expand Down
Loading
Loading