Add Korean Japanese and English user interfaces
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
export { classifyClipboard, clipboardCharacterCount } from './clipboard.js';
|
||||
export {
|
||||
SUPPORTED_LANGUAGES,
|
||||
normalizeLanguage,
|
||||
preferredLanguage,
|
||||
initialLanguage,
|
||||
translate,
|
||||
translateSystemMessage,
|
||||
} from './localization.js';
|
||||
export const DEVICE_NAME_MAX_LENGTH = 40;
|
||||
export const PASSWORD_MIN_LENGTH = 8;
|
||||
export const devicePatch = z
|
||||
@@ -116,7 +124,13 @@ export type Space = {
|
||||
expiresAt: number;
|
||||
members: { device: Device; status: 'pending' | 'approved' }[];
|
||||
};
|
||||
export type SessionResponse = { device: Device; csrfToken: string; expiresAt: number };
|
||||
export type Language = 'ko' | 'ja' | 'en';
|
||||
export type SessionResponse = {
|
||||
device: Device;
|
||||
csrfToken: string;
|
||||
expiresAt: number;
|
||||
defaultLanguage?: Language;
|
||||
};
|
||||
export function parseUrls(value: string): { urls: string[]; invalid: string[] } {
|
||||
const entries = value
|
||||
.split(/\r?\n/)
|
||||
|
||||
69
packages/shared/src/localization.js
Normal file
69
packages/shared/src/localization.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { messages } from './messages.js';
|
||||
|
||||
export const SUPPORTED_LANGUAGES = ['ko', 'ja', 'en'];
|
||||
export function normalizeLanguage(value) {
|
||||
const language = typeof value === 'string' ? value.trim().toLowerCase().split(/[-_]/)[0] : '';
|
||||
return SUPPORTED_LANGUAGES.includes(language) ? language : null;
|
||||
}
|
||||
export function preferredLanguage(header, fallback = 'en') {
|
||||
if (typeof header !== 'string') return fallback;
|
||||
const choices = header
|
||||
.slice(0, 1024)
|
||||
.split(',')
|
||||
.map((part, order) => {
|
||||
const [tag, ...parameters] = part.trim().split(';');
|
||||
const quality = parameters.find((parameter) => parameter.trim().startsWith('q='));
|
||||
const weight = quality ? Number(quality.trim().slice(2)) : 1;
|
||||
return { language: normalizeLanguage(tag), weight, order };
|
||||
});
|
||||
return (
|
||||
choices
|
||||
.filter(({ language, weight }) => language && weight > 0 && weight <= 1)
|
||||
.sort((a, b) => b.weight - a.weight || a.order - b.order)[0]?.language ?? fallback
|
||||
);
|
||||
}
|
||||
export function initialLanguage(country, browserLanguages) {
|
||||
if (country === 'KR') return 'ko';
|
||||
if (country === 'JP') return 'ja';
|
||||
if (country && /^[A-Z]{2}$/.test(country) && country !== 'XX') return 'en';
|
||||
return preferredLanguage(browserLanguages);
|
||||
}
|
||||
const interpolate = (message, values) =>
|
||||
message.replace(/\{(p\d+)\}/g, (match, key) =>
|
||||
Object.hasOwn(values, key) ? String(values[key] ?? '') : match,
|
||||
);
|
||||
export function translate(message, language = 'ko', values = {}) {
|
||||
const text = language === 'ko' ? message : (messages[message]?.[language] ?? message);
|
||||
return interpolate(text, values);
|
||||
}
|
||||
const sourceMessages = new Map(
|
||||
Object.entries(messages).flatMap(([key, translations]) =>
|
||||
[key, ...Object.values(translations)].map((text) => [text, key]),
|
||||
),
|
||||
);
|
||||
const patterns = Object.keys(messages)
|
||||
.filter((key) => /\{p\d+\}/.test(key))
|
||||
.flatMap((key) =>
|
||||
[key, ...Object.values(messages[key])].map((text) => {
|
||||
const names = [...text.matchAll(/\{(p\d+)\}/g)].map((match) => match[1]);
|
||||
const escaped = text
|
||||
.split(/\{p\d+\}/)
|
||||
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
return { key, names, pattern: new RegExp('^' + escaped.join('([\\s\\S]*?)') + '$') };
|
||||
}),
|
||||
);
|
||||
// Apply only to system messages, never filenames, device names or transferred text.
|
||||
export function translateSystemMessage(message, language) {
|
||||
const source = sourceMessages.get(message);
|
||||
if (source) return translate(source, language);
|
||||
for (const { key, names, pattern } of patterns) {
|
||||
const match = pattern.exec(message);
|
||||
if (match)
|
||||
return translate(
|
||||
key,
|
||||
language,
|
||||
Object.fromEntries(names.map((name, i) => [name, match[i + 1]])),
|
||||
);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
1687
packages/shared/src/messages.js
Normal file
1687
packages/shared/src/messages.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user