Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add comprehensive login page customization options#7374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
strickvl wants to merge15 commits intocoder:main
base:main
Choose a base branch
Loading
fromstrickvl:main
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
15 commits
Select commitHold shift + click to select a range
cd99956
feat: add comprehensive login page customization options
strickvlJun 12, 2025
bac8b42
Merge branch 'main' into main
strickvlJun 17, 2025
2553f45
feat: replace individual UI flags with unified --custom-strings flag
strickvlJun 17, 2025
c2eb61d
docs: simplify migration guide to only cover released flags
strickvlJun 17, 2025
c0189ed
docs: update Docker customization examples to use --custom-strings
strickvlJun 17, 2025
cacf3cc
Merge branch 'main' into main
strickvlJun 21, 2025
2b2819e
docs: remove niche customization sections from FAQ and install guide
strickvlJun 24, 2025
b275d52
refactor: remove redundant custom-strings validation and global variable
strickvlJun 24, 2025
af2f599
refactor: simplify loadCustomStrings function signature and error han…
strickvlJun 24, 2025
e89a9af
test: remove outdated tests for deprecated custom UI flags
strickvlJun 24, 2025
a8b7fbe
refactor: use addResourceBundle instead of re-initializing i18next
strickvlJun 24, 2025
27ee714
docs: improve custom-strings flag description
strickvlJun 24, 2025
d5c0c88
refactor: keep --app-name flag as non-deprecated for {{app}} placeholder
strickvlJun 24, 2025
fd6ca51
refactor: rename --custom-strings flag to --i18n
strickvlJun 24, 2025
ca12a25
docs: consolidate i18n documentation in guide.md
strickvlJun 24, 2025
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
1 change: 1 addition & 0 deletionsdocs/FAQ.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ You can change the config file's location using the `--config` flag or

The default location respects `$XDG_CONFIG_HOME`.


## How do I make my keyboard shortcuts work?

Many shortcuts will not work by default, since they'll be "caught" by the browser.
Expand Down
1 change: 1 addition & 0 deletionsdocs/README.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ code-server.
We also have an in-depth [setup and
configuration](https://coder.com/docs/code-server/latest/guide) guide.


## Questions?

See answers to [frequently asked
Expand Down
35 changes: 35 additions & 0 deletionsdocs/guide.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -458,3 +458,38 @@ By default, if you have auth enabled, code-server will authenticate all proxied
requests including preflight requests. This can cause issues because preflight
requests do not typically include credentials. To allow all preflight requests
through the proxy without authentication, use `--skip-auth-preflight`.

## Internationalization and customization

code-server allows you to provide a language file or JSON to configure certain strings. This can be used for both internationalization and customization.

For example:

```shell
code-server --i18n /custom-strings.json
code-server --i18n '{"WELCOME": "{{app}} ログイン"}'
```

Or this can be done in the config file:

```yaml
i18n: |
{
"WELCOME": "Welcome to the {{app}} Development Portal"
}
```

You can combine this with the `--locale` flag to configure language support for both code-server and VS Code in cases where code-server has no support but VS Code does. If you are using this for internationalization, please consider sending us a pull request to contribute it to `src/node/i18n/locales`.

### Available keys and placeholders

Refer to [../src/node/i18n/locales/en.json](../src/node/i18n/locales/en.json) for a full list of the available keys for translations. Note that the only placeholders supported for each key are the ones used in the default string.

The `--app-name` flag controls the `{{app}}` placeholder in templates. If you want to change the name, you can either:

1. Set `--app-name` (potentially alongside `--i18n`)
2. Use `--i18n` and hardcode the name in your strings

### Legacy flag

The `--welcome-text` flag is now deprecated. Use the `WELCOME` key instead.
1 change: 1 addition & 0 deletionsdocs/install.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -287,6 +287,7 @@ docker run -it --name code-server -p 127.0.0.1:8080:8080 \
codercom/code-server:latest
```


Our official image supports `amd64` and `arm64`. For `arm32` support, you can
use a [community-maintained code-server
alternative](https://hub.docker.com/r/linuxserver/code-server).
Expand Down
10 changes: 9 additions & 1 deletionsrc/node/cli.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
"app-name"?: string
"welcome-text"?: string
"abs-proxy-base-path"?: string
i18n?: string
/* Positional arguments. */
_?: string[]
}
Expand DownExpand Up@@ -284,17 +285,22 @@ export const options: Options<Required<UserProvidedArgs>> = {
"app-name": {
type: "string",
short: "an",
description: "The name to useinbranding. Will be shown in titlebar and welcome message",
description: "Will replace the {{app}} placeholderinany strings, which by default includes the title bar and welcome message",
},
"welcome-text": {
type: "string",
short: "w",
description: "Text to show on login page",
deprecated: true,
},
"abs-proxy-base-path": {
type: "string",
description: "The base path to prefix to all absproxy requests",
},
i18n: {
type: "string",
description: "Path to JSON file or raw JSON string with custom translations. Merges with default strings and supports all i18n keys.",
},
}

export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
Expand DownExpand Up@@ -459,6 +465,7 @@ export const parse = (
throw new Error("--cert-key is missing")
}


logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))])

return args
Expand DownExpand Up@@ -593,6 +600,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["disable-proxy"] = true
}


const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD
Expand Down
61 changes: 44 additions & 17 deletionssrc/node/i18n/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,60 @@
import i18next, { init } from "i18next"
import { promises as fs } from "fs"
import * as en from "./locales/en.json"
import * as ja from "./locales/ja.json"
import * as th from "./locales/th.json"
import * as ur from "./locales/ur.json"
import * as zhCn from "./locales/zh-cn.json"

const defaultResources = {
en: {
translation: en,
},
"zh-cn": {
translation: zhCn,
},
th: {
translation: th,
},
ja: {
translation: ja,
},
ur: {
translation: ur,
},
}


export async function loadCustomStrings(customStringsArg: string): Promise<void> {

try {
let customStringsData: Record<string, any>

// Try to parse as JSON first
try {
customStringsData = JSON.parse(customStringsArg)
} catch {
// If JSON parsing fails, treat as file path
const fileContent = await fs.readFile(customStringsArg, "utf8")
customStringsData = JSON.parse(fileContent)
}

// User-provided strings override all languages.
Object.keys(defaultResources).forEach((locale) => {
i18next.addResourceBundle(locale, "translation", customStringsData)
})
} catch (error) {
throw new Error(`Failed to load custom strings: ${error instanceof Error ? error.message : String(error)}`)
}
}

init({
lng: "en",
fallbackLng: "en", // language to use if translations in user language are not available.
returnNull: false,
lowerCaseLng: true,
debug: process.env.NODE_ENV === "development",
resources: {
en: {
translation: en,
},
"zh-cn": {
translation: zhCn,
},
th: {
translation: th,
},
ja: {
translation: ja,
},
ur: {
translation: ur,
},
},
resources: defaultResources,
})

export default i18next
7 changes: 7 additions & 0 deletionssrc/node/main.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@ import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature, toCodeArgs, UserProvidedArgs } from "./cli"
import { commit, version, vsRootPath } from "./constants"
import { loadCustomStrings } from "./i18n"
import { register } from "./routes"
import { VSCodeModule } from "./routes/vscode"
import { isDirectory, open } from "./util"
Expand DownExpand Up@@ -122,6 +123,12 @@ export const runCodeServer = async (
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
logger.info(`code-server ${version} ${commit}`)

// Load custom strings if provided
if (args.i18n) {
await loadCustomStrings(args.i18n)
logger.info("Loaded custom strings")
}

logger.info(`Using user-data-dir ${args["user-data-dir"]}`)
logger.debug(`Using extensions-dir ${args["extensions-dir"]}`)

Expand Down
19 changes: 14 additions & 5 deletionssrc/node/routes/login.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,23 +31,32 @@ const getRoot = async (req: Request, error?: Error): Promise<string> => {
const locale = req.args["locale"] || "en"
i18n.changeLanguage(locale)
const appName = req.args["app-name"] || "code-server"
const welcomeText = req.args["welcome-text"] || (i18n.t("WELCOME", { app: appName }) as string)
const welcomeText = escapeHtml(req.args["welcome-text"] || (i18n.t("WELCOME", { app: appName }) as string))

// Determine password message using i18n
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: req.args.config })
if (req.args.usingEnvPassword) {
passwordMsg = i18n.t("LOGIN_USING_ENV_PASSWORD")
} else if (req.args.usingEnvHashedPassword) {
passwordMsg = i18n.t("LOGIN_USING_HASHED_PASSWORD")
}
passwordMsg = escapeHtml(passwordMsg)

// Get messages from i18n (with HTML escaping for security)
const loginTitle = escapeHtml(i18n.t("LOGIN_TITLE", { app: appName }))
const loginBelow = escapeHtml(i18n.t("LOGIN_BELOW"))
const passwordPlaceholder = escapeHtml(i18n.t("PASSWORD_PLACEHOLDER"))
const submitText = escapeHtml(i18n.t("SUBMIT"))

return replaceTemplates(
req,
content
.replace(/{{I18N_LOGIN_TITLE}}/g,i18n.t("LOGIN_TITLE", { app: appName }))
.replace(/{{I18N_LOGIN_TITLE}}/g,loginTitle)
.replace(/{{WELCOME_TEXT}}/g, welcomeText)
.replace(/{{PASSWORD_MSG}}/g, passwordMsg)
.replace(/{{I18N_LOGIN_BELOW}}/g,i18n.t("LOGIN_BELOW"))
.replace(/{{I18N_PASSWORD_PLACEHOLDER}}/g,i18n.t("PASSWORD_PLACEHOLDER"))
.replace(/{{I18N_SUBMIT}}/g,i18n.t("SUBMIT"))
.replace(/{{I18N_LOGIN_BELOW}}/g,loginBelow)
.replace(/{{I18N_PASSWORD_PLACEHOLDER}}/g,passwordPlaceholder)
.replace(/{{I18N_SUBMIT}}/g,submitText)
.replace(/{{ERROR}}/, error ? `<div class="error">${escapeHtml(error.message)}</div>` : ""),
)
}
Expand Down
27 changes: 27 additions & 0 deletionstest/unit/node/cli.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ describe("parser", () => {
"--verbose",
["--app-name", "custom instance name"],
["--welcome-text", "welcome to code"],
["--i18n", '{"LOGIN_TITLE": "Custom Portal"}'],
"2",

["--locale", "ja"],
Expand DownExpand Up@@ -145,6 +146,7 @@ describe("parser", () => {
verbose: true,
"app-name": "custom instance name",
"welcome-text": "welcome to code",
i18n: '{"LOGIN_TITLE": "Custom Portal"}',
version: true,
"bind-addr": "192.169.0.1:8080",
"session-socket": "/tmp/override-code-server-ipc-socket",
Expand DownExpand Up@@ -347,6 +349,31 @@ describe("parser", () => {
})
})

it("should parse i18n flag", async () => {
// Test with JSON string
const jsonString = '{"WELCOME": "Custom Welcome", "LOGIN_TITLE": "My App"}'
const args = parse(["--i18n", jsonString])
expect(args).toEqual({
i18n: jsonString,
})
})

it("should parse i18n file paths and JSON", async () => {
// Test with valid JSON that looks like a file path
expect(() => parse(["--i18n", "/path/to/file.json"])).not.toThrow()

// Test with JSON string (no validation at CLI level)
expect(() => parse(["--i18n", '{"valid": "json"}'])).not.toThrow()
})

it("should support app-name and deprecated welcome-text flags", async () => {
const args = parse(["--app-name", "My App", "--welcome-text", "Welcome!"])
expect(args).toEqual({
"app-name": "My App",
"welcome-text": "Welcome!",
})
})

it("should use env var github token", async () => {
process.env.GITHUB_TOKEN = "ga-foo"
const args = parse([])
Expand Down
1 change: 1 addition & 0 deletionstest/unit/node/routes/login.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,5 +146,6 @@ describe("login", () => {
expect(resp.status).toBe(200)
expect(htmlContent).toContain(`欢迎来到 code-server`)
})

})
})
Loading

[8]ページ先頭

©2009-2025 Movatter.jp