Files
OpenFrontIO/src/client/UsernameInput.ts
T
Aotumuri 9088adeb7a Translate all out-of-game UI (start screen, lobbies, etc.) (#316)
This PR adds full translation support for all out-of-game UI elements,
including the start screen, public/private lobby modals, and other
pre-game interfaces.

All static text has been externalized to en.json and ja.json for future
language support.
If you find any spots that are not yet translated (missing from the
JSON), please let me know.
Thanks a lot!

This is a follow-up to PR
[#305](https://github.com/openfrontio/OpenFrontIO/pull/305).

---------

Co-authored-by: Cldprv <dubois.cnm@tutanota.com>
Co-authored-by: jacks0n <rosty.west89@gmail.com>
2025-03-24 17:12:04 -07:00

113 lines
3.2 KiB
TypeScript

import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { v4 as uuidv4 } from "uuid";
import {
MAX_USERNAME_LENGTH,
validateUsername,
} from "../core/validations/username";
import { UserSettings } from "../core/game/UserSettings";
import { translateText } from "../client/Utils";
const usernameKey: string = "username";
@customElement("username-input")
export class UsernameInput extends LitElement {
@state() private username: string = "";
@property({ type: String }) validationError: string = "";
private _isValid: boolean = true;
private userSettings: UserSettings = new UserSettings();
// Remove static styles since we're using Tailwind
createRenderRoot() {
// Disable shadow DOM to allow Tailwind classes to work
return this;
}
public getCurrentUsername(): string {
return this.username;
}
connectedCallback() {
super.connectedCallback();
this.username = this.getStoredUsername();
this.dispatchUsernameEvent();
}
render() {
return html`
<input
type="text"
.value=${this.username}
@input=${this.handleChange}
@change=${this.handleChange}
placeholder="${translateText("username.enter_username")}"
maxlength="${MAX_USERNAME_LENGTH}"
class="w-full px-4 py-2 border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
/>
${this.validationError
? html`<div
class="mt-2 px-3 py-1 text-lg border rounded bg-white text-red-600 border-red-600 dark:bg-gray-700 dark:text-red-300 dark:border-red-300"
>
${this.validationError}
</div>`
: null}
`;
}
private handleChange(e: Event) {
const input = e.target as HTMLInputElement;
this.username = input.value.trim();
const result = validateUsername(this.username);
this._isValid = result.isValid;
if (result.isValid) {
this.storeUsername(this.username);
this.validationError = "";
} else {
this.validationError = result.error;
}
}
private getStoredUsername(): string {
const storedUsername = localStorage.getItem(usernameKey);
if (storedUsername) {
return storedUsername;
}
return this.generateNewUsername();
}
private storeUsername(username: string) {
if (username) {
localStorage.setItem(usernameKey, username);
}
}
private dispatchUsernameEvent() {
this.dispatchEvent(
new CustomEvent("username-change", {
detail: { username: this.username },
bubbles: true,
composed: true,
}),
);
}
private generateNewUsername(): string {
const newUsername = "Anon" + this.uuidToThreeDigits();
this.storeUsername(newUsername);
return newUsername;
}
private uuidToThreeDigits(): string {
const uuid = uuidv4();
const cleanUuid = uuid.replace(/-/g, "").toLowerCase();
const decimal = BigInt(`0x${cleanUuid}`);
const threeDigits = decimal % 1000n;
return threeDigits.toString().padStart(3, "0");
}
public isValid(): boolean {
return this._isValid;
}
}