From 52c33818fabc09440ab500ba3786e2f7e22ed5f4 Mon Sep 17 00:00:00 2001 From: Besser Sehen Landshut Date: Thu, 20 Aug 2026 14:25:30 +0200 Subject: [PATCH] fix: allow non-Latin characters in display names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registering with a display name such as "Jürgen Müller" fails with "Erlaubt sind a-z, 0-9, - . _". Two checks reject it, both using \w, which means [A-Za-z0-9_] in JavaScript as well as in Go: ui/.../SignUpForm/index.tsx const nameRegex = /^[\w.-\s]{2,30}$/ pkg/checker/username.go usernameReg = `^[\w.\- ]{2,30}$` This affects every language except English. The second check is not really about the display name: MakeUsername derives the username from it, and the username ends up in a profile URL, so it is restricted on purpose. The restriction is simply applied one step too early. Answer already solves this elsewhere. Question URLs are transliterated in pkg/htmltext via unidecode.Unidecode, and MakeUsername already handles Chinese through pinyin. This change carries that idea through: - transliterate the display name before deriving the username, using the dependency the project already ships (go-unidecode) - drop what transliteration may leave behind — "Ольга" becomes "Ol'ga", and an apostrophe is not a valid username character - widen the front-end check to Unicode letters and digits The display name keeps its original spelling; only the derived username is transliterated: Jürgen Müller -> jurgen-muller José García -> jose-garcia Ольга -> olga Ελένη -> elene Straße -> strasse Chinese input is unaffected — pinyin still runs first. --- internal/service/user_common/user.go | 17 +++++++++++++++++ .../Register/components/SignUpForm/index.tsx | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go index 2e777f14c..b2c7d9c6d 100644 --- a/internal/service/user_common/user.go +++ b/internal/service/user_common/user.go @@ -21,6 +21,7 @@ package usercommon import ( "context" + "regexp" "strings" "github.com/apache/answer/internal/base/constant" @@ -35,6 +36,7 @@ import ( "github.com/apache/answer/pkg/checker" "github.com/apache/answer/pkg/random" "github.com/mozillazg/go-pinyin" + "github.com/mozillazg/go-unidecode" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" ) @@ -190,6 +192,10 @@ func (us *UserCommon) FormatUserBasicInfo(ctx context.Context, userInfo *entity. return userBasicInfo } +// usernameDisallowed matches everything checker.IsInvalidUsername rejects. +// Kept in sync with pkg/checker/username.go. +var usernameDisallowed = regexp.MustCompile(`[^\w.\- ]`) + // MakeUsername // Generate a unique Username based on the displayName func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (username string, err error) { @@ -198,6 +204,17 @@ func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (use displayName = strings.Join(pinyin.LazyConvert(displayName, nil), "") } + // Other non-Latin characters, such as German umlauts or accented + // letters, are transliterated the same way question URLs are (see + // pkg/htmltext). Without this, a display name like "Jürgen Müller" + // would produce an invalid username and registration would fail. + displayName = unidecode.Unidecode(displayName) + + // Transliteration can emit characters a username may not contain: + // "Ольга" becomes "Ol'ga", for example. Drop those, so the result + // still satisfies checker.IsInvalidUsername below. + displayName = usernameDisallowed.ReplaceAllString(displayName, "") + username = strings.ReplaceAll(displayName, " ", "-") username = strings.ToLower(username) suffix := "" diff --git a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx index 8dc30b965..c01da32c5 100644 --- a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx +++ b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx @@ -57,7 +57,7 @@ const Index: React.FC = ({ callback }) => { }); const emailCaptcha = useCaptchaPlugin('email'); - const nameRegex = /^[\w.-\s]{2,30}$/; + const nameRegex = /^[\p{L}\p{N}._\s-]{2,30}$/u; const handleChange = (params: FormDataType) => { setFormData({ ...formData, ...params });