refactor: translate error messages from Chinese to English

Standardize all user-facing error/validation messages across
partition-table, nvs, app-image, and ota-data parsers to English.
This commit is contained in:
kerms 2026-02-28 11:50:00 +01:00
parent d32674617d
commit 4959ff74c1
Signed by: kerms
GPG Key ID: 5432C10DDCF8DAD5
9 changed files with 57 additions and 57 deletions

View File

@ -15,13 +15,13 @@ import {
*/ */
export function parseAppImage(data: Uint8Array): AppImageInfo { export function parseAppImage(data: Uint8Array): AppImageInfo {
if (data.length < IMAGE_HEADER_SIZE + EXTENDED_HEADER_SIZE) { if (data.length < IMAGE_HEADER_SIZE + EXTENDED_HEADER_SIZE) {
throw new Error(`数据太短: ${data.length} 字节 (最少需要 ${IMAGE_HEADER_SIZE + EXTENDED_HEADER_SIZE} 字节)`); throw new Error(`Image too short: ${data.length}B`);
} }
// ── Image Header (8 bytes: magic + segments + spi_mode + spi_speed_size + entry_addr) ── // ── Image Header (8 bytes: magic + segments + spi_mode + spi_speed_size + entry_addr) ──
const magic = readU8(data, 0); const magic = readU8(data, 0);
if (magic !== IMAGE_MAGIC) { if (magic !== IMAGE_MAGIC) {
throw new Error(`无效的魔数: 0x${magic.toString(16)} (应为 0xE9)`); throw new Error(`Invalid image magic: 0x${magic.toString(16)}`);
} }
const segmentCount = readU8(data, 1); const segmentCount = readU8(data, 1);

View File

@ -66,10 +66,10 @@ interface ParsedPage {
*/ */
export function parseBinary(data: Uint8Array): NvsPartition { export function parseBinary(data: Uint8Array): NvsPartition {
if (data.length % PAGE_SIZE !== 0) { if (data.length % PAGE_SIZE !== 0) {
throw new Error(`二进制数据大小 (${data.length}) 不是页大小 (${PAGE_SIZE}) 的倍数`); throw new Error(`Invalid NVS size: ${data.length}B`);
} }
if (data.length === 0) { if (data.length === 0) {
throw new Error('二进制数据为空'); throw new Error('NVS data is empty');
} }
const pageCount = data.length / PAGE_SIZE; const pageCount = data.length / PAGE_SIZE;

View File

@ -62,10 +62,10 @@ interface PlannedEntry {
*/ */
export function serializeBinary(partition: NvsPartition, targetSize: number): Uint8Array { export function serializeBinary(partition: NvsPartition, targetSize: number): Uint8Array {
if (targetSize % PAGE_SIZE !== 0) { if (targetSize % PAGE_SIZE !== 0) {
throw new Error(`目标大小 (${targetSize}) 不是页大小 (${PAGE_SIZE}) 的倍数`); throw new Error(`Target size (${targetSize}) is not a multiple of page size (${PAGE_SIZE})`);
} }
if (targetSize < MIN_PARTITION_SIZE) { if (targetSize < MIN_PARTITION_SIZE) {
throw new Error(`目标大小 (${targetSize}) 小于最小分区大小 (${MIN_PARTITION_SIZE})`); throw new Error(`Target size (${targetSize}) is less than minimum partition size (${MIN_PARTITION_SIZE})`);
} }
// Allocate buffer filled with 0xFF (erased flash state) // Allocate buffer filled with 0xFF (erased flash state)
@ -328,8 +328,8 @@ export function serializeBinary(partition: NvsPartition, targetSize: number): Ui
if (plannedIdx < planned.length) { if (plannedIdx < planned.length) {
throw new Error( throw new Error(
`分区空间不足: 还有 ${planned.length - plannedIdx} 个条目无法写入。` + `Partition space exhausted: ${planned.length - plannedIdx} entries could not be written. ` +
`请增大分区大小。` `Increase partition size.`
); );
} }

View File

@ -40,17 +40,17 @@ function parseIntValue(str: string): number {
let val: number; let val: number;
if (str.startsWith('0x') || str.startsWith('0X')) { if (str.startsWith('0x') || str.startsWith('0X')) {
if (!/^-?0[xX][0-9a-fA-F]+$/.test(str)) { if (!/^-?0[xX][0-9a-fA-F]+$/.test(str)) {
throw new Error(`无效的整数值: "${str}"`); throw new Error(`Invalid integer value: "${str}"`);
} }
val = parseInt(str, 16); val = parseInt(str, 16);
} else { } else {
if (!/^-?\d+$/.test(str)) { if (!/^-?\d+$/.test(str)) {
throw new Error(`无效的整数值: "${str}"`); throw new Error(`Invalid integer value: "${str}"`);
} }
val = parseInt(str, 10); val = parseInt(str, 10);
} }
if (Number.isNaN(val)) { if (Number.isNaN(val)) {
throw new Error(`无效的整数值: "${str}"`); throw new Error(`Invalid integer value: "${str}"`);
} }
return val; return val;
} }
@ -62,14 +62,14 @@ function parseBigIntValue(str: string): bigint {
// Handle negative hex explicitly. // Handle negative hex explicitly.
if (str.startsWith('-0x') || str.startsWith('-0X')) { if (str.startsWith('-0x') || str.startsWith('-0X')) {
if (!/^-0[xX][0-9a-fA-F]+$/.test(str)) { if (!/^-0[xX][0-9a-fA-F]+$/.test(str)) {
throw new Error(`无效的整数值: "${str}"`); throw new Error(`Invalid integer value: "${str}"`);
} }
return -BigInt(str.slice(1)); return -BigInt(str.slice(1));
} }
try { try {
return BigInt(str); return BigInt(str);
} catch { } catch {
throw new Error(`无效的整数值: "${str}"`); throw new Error(`Invalid integer value: "${str}"`);
} }
} }
@ -169,20 +169,20 @@ export function parseCsv(text: string): NvsPartition {
} }
if (!currentNamespace) { if (!currentNamespace) {
throw new Error(`${i + 1}: 数据条目 "${key}" 出现在任何命名空间之前`); throw new Error(`Line ${i + 1}: data entry "${key}" appears before any namespace`);
} }
if (type !== 'data' && type !== 'file') { if (type !== 'data' && type !== 'file') {
throw new Error(`${i + 1}: 未知类型 "${type}"`); throw new Error(`Line ${i + 1}: unknown type "${type}"`);
} }
if (!encoding) { if (!encoding) {
throw new Error(`${i + 1}: 键 "${key}" 缺少编码类型`); throw new Error(`Line ${i + 1}: key "${key}" missing encoding`);
} }
const nvsType = ENCODING_TO_TYPE[encoding as NvsEncoding]; const nvsType = ENCODING_TO_TYPE[encoding as NvsEncoding];
if (nvsType === undefined) { if (nvsType === undefined) {
throw new Error(`${i + 1}: 未知编码 "${encoding}"`); throw new Error(`Line ${i + 1}: unknown encoding "${encoding}"`);
} }
let parsedValue: number | bigint | string | Uint8Array; let parsedValue: number | bigint | string | Uint8Array;

View File

@ -182,27 +182,27 @@ export function validatePartition(partition: NvsPartition): string[] {
const errors: string[] = []; const errors: string[] = [];
if (partition.namespaces.length > MAX_NAMESPACES) { if (partition.namespaces.length > MAX_NAMESPACES) {
errors.push(`命名空间数量超过上限 ${MAX_NAMESPACES}`); errors.push(`Namespace count exceeds limit ${MAX_NAMESPACES}`);
} }
for (const ns of partition.namespaces) { for (const ns of partition.namespaces) {
if (ns.length === 0) { if (ns.length === 0) {
errors.push('命名空间名称不能为空'); errors.push('Namespace name cannot be empty');
} }
if (ns.length > MAX_KEY_LENGTH) { if (ns.length > MAX_KEY_LENGTH) {
errors.push(`命名空间 "${ns}" 名称超过 ${MAX_KEY_LENGTH} 字符`); errors.push(`Namespace "${ns}" exceeds ${MAX_KEY_LENGTH} characters`);
} }
} }
for (const entry of partition.entries) { for (const entry of partition.entries) {
if (entry.key.length === 0) { if (entry.key.length === 0) {
errors.push(`在命名空间 "${entry.namespace}" 中存在空键名`); errors.push(`Empty key in namespace "${entry.namespace}"`);
} }
if (entry.key.length > MAX_KEY_LENGTH) { if (entry.key.length > MAX_KEY_LENGTH) {
errors.push(`键 "${entry.key}" 名称超过 ${MAX_KEY_LENGTH} 字符`); errors.push(`Key "${entry.key}" exceeds ${MAX_KEY_LENGTH} characters`);
} }
if (!partition.namespaces.includes(entry.namespace)) { if (!partition.namespaces.includes(entry.namespace)) {
errors.push(`键 "${entry.key}" 的命名空间 "${entry.namespace}" 未注册`); errors.push(`Key "${entry.key}" references unregistered namespace "${entry.namespace}"`);
} }
// Validate value ranges for primitive types // Validate value ranges for primitive types
@ -210,21 +210,21 @@ export function validatePartition(partition: NvsPartition): string[] {
if (typeof entry.value === 'number') { if (typeof entry.value === 'number') {
const v = entry.value; const v = entry.value;
switch (entry.type) { switch (entry.type) {
case NvsType.U8: if (v < 0 || v > 0xFF) errors.push(`"${entry.key}" U8 值超出范围`); break; case NvsType.U8: if (v < 0 || v > 0xFF) errors.push(`"${entry.key}" U8 value out of range`); break;
case NvsType.I8: if (v < -128 || v > 127) errors.push(`"${entry.key}" I8 值超出范围`); break; case NvsType.I8: if (v < -128 || v > 127) errors.push(`"${entry.key}" I8 value out of range`); break;
case NvsType.U16: if (v < 0 || v > 0xFFFF) errors.push(`"${entry.key}" U16 值超出范围`); break; case NvsType.U16: if (v < 0 || v > 0xFFFF) errors.push(`"${entry.key}" U16 value out of range`); break;
case NvsType.I16: if (v < -32768 || v > 32767) errors.push(`"${entry.key}" I16 值超出范围`); break; case NvsType.I16: if (v < -32768 || v > 32767) errors.push(`"${entry.key}" I16 value out of range`); break;
case NvsType.U32: if (v < 0 || v > 0xFFFFFFFF) errors.push(`"${entry.key}" U32 值超出范围`); break; case NvsType.U32: if (v < 0 || v > 0xFFFFFFFF) errors.push(`"${entry.key}" U32 value out of range`); break;
case NvsType.I32: if (v < -2147483648 || v > 2147483647) errors.push(`"${entry.key}" I32 值超出范围`); break; case NvsType.I32: if (v < -2147483648 || v > 2147483647) errors.push(`"${entry.key}" I32 value out of range`); break;
} }
} else if (typeof entry.value === 'bigint') { } else if (typeof entry.value === 'bigint') {
const v = entry.value; const v = entry.value;
switch (entry.type) { switch (entry.type) {
case NvsType.U64: case NvsType.U64:
if (v < 0n || v > 0xFFFFFFFFFFFFFFFFn) errors.push(`"${entry.key}" U64 值超出范围`); if (v < 0n || v > 0xFFFFFFFFFFFFFFFFn) errors.push(`"${entry.key}" U64 value out of range`);
break; break;
case NvsType.I64: case NvsType.I64:
if (v < -9223372036854775808n || v > 9223372036854775807n) errors.push(`"${entry.key}" I64 值超出范围`); if (v < -9223372036854775808n || v > 9223372036854775807n) errors.push(`"${entry.key}" I64 value out of range`);
break; break;
} }
} }
@ -234,7 +234,7 @@ export function validatePartition(partition: NvsPartition): string[] {
if (entry.type === NvsType.SZ && typeof entry.value === 'string') { if (entry.type === NvsType.SZ && typeof entry.value === 'string') {
const byteLen = new TextEncoder().encode(entry.value).length; const byteLen = new TextEncoder().encode(entry.value).length;
if (byteLen >= MAX_STRING_LENGTH) { if (byteLen >= MAX_STRING_LENGTH) {
errors.push(`"${entry.key}" 字符串长度 ${byteLen} 字节超过上限 ${MAX_STRING_LENGTH - 1}`); errors.push(`"${entry.key}" string length ${byteLen} bytes exceeds limit ${MAX_STRING_LENGTH - 1}`);
} }
} }
@ -244,11 +244,11 @@ export function validatePartition(partition: NvsPartition): string[] {
// NvsType.BLOB_DATA uses the V2 chunked format and is capped at MAX_BLOB_SIZE_V2. // NvsType.BLOB_DATA uses the V2 chunked format and is capped at MAX_BLOB_SIZE_V2.
if (entry.type === NvsType.BLOB && entry.value instanceof Uint8Array) { if (entry.type === NvsType.BLOB && entry.value instanceof Uint8Array) {
if (entry.value.length > MAX_BLOB_SIZE_V1) { if (entry.value.length > MAX_BLOB_SIZE_V1) {
errors.push(`"${entry.key}" BLOB ${entry.value.length} 字节超过上限 ${MAX_BLOB_SIZE_V1}`); errors.push(`"${entry.key}" BLOB ${entry.value.length} bytes exceeds limit ${MAX_BLOB_SIZE_V1}`);
} }
} else if (entry.type === NvsType.BLOB_DATA && entry.value instanceof Uint8Array) { } else if (entry.type === NvsType.BLOB_DATA && entry.value instanceof Uint8Array) {
if (entry.value.length > MAX_BLOB_SIZE_V2) { if (entry.value.length > MAX_BLOB_SIZE_V2) {
errors.push(`"${entry.key}" BLOB ${entry.value.length} 字节超过 V2 上限 ${MAX_BLOB_SIZE_V2}`); errors.push(`"${entry.key}" BLOB ${entry.value.length} bytes exceeds V2 limit ${MAX_BLOB_SIZE_V2}`);
} }
} }
} }
@ -258,7 +258,7 @@ export function validatePartition(partition: NvsPartition): string[] {
for (const entry of partition.entries) { for (const entry of partition.entries) {
const k = `${entry.namespace}::${entry.key}`; const k = `${entry.namespace}::${entry.key}`;
if (seen.has(k)) { if (seen.has(k)) {
errors.push(`重复键: ${entry.namespace}/${entry.key}`); errors.push(`Duplicate key: ${entry.namespace}/${entry.key}`);
} }
seen.add(k); seen.add(k);
} }

View File

@ -44,7 +44,7 @@ function isEntryValid(entry: OtaSelectEntry): boolean {
*/ */
export function parseOtaData(data: Uint8Array, numOtaPartitions = DEFAULT_NUM_OTA_PARTITIONS): OtaData { export function parseOtaData(data: Uint8Array, numOtaPartitions = DEFAULT_NUM_OTA_PARTITIONS): OtaData {
if (data.length < OTA_DATA_MIN_SIZE) { if (data.length < OTA_DATA_MIN_SIZE) {
throw new Error(`OTA data too short: ${data.length} bytes, need at least ${OTA_DATA_MIN_SIZE}`); throw new Error(`OTA data too short: ${data.length}B`);
} }
const entry0 = parseEntry(data, 0); const entry0 = parseEntry(data, 0);

View File

@ -16,7 +16,7 @@ function parseFlags(str: string): number {
for (const part of normalized.split(/[\s|,]+/).filter(Boolean)) { for (const part of normalized.split(/[\s|,]+/).filter(Boolean)) {
if (part === 'encrypted') result |= PartitionFlags.ENCRYPTED; if (part === 'encrypted') result |= PartitionFlags.ENCRYPTED;
else if (part === 'readonly') result |= PartitionFlags.READONLY; else if (part === 'readonly') result |= PartitionFlags.READONLY;
else throw new Error(`未知标志: "${part}"`); else throw new Error(`Unknown flag: "${part}"`);
} }
return result; return result;
} }
@ -26,9 +26,9 @@ function parseSize(str: string): number {
str = str.trim(); str = str.trim();
if (!str) return 0; if (!str) return 0;
if (str.startsWith('0x') || str.startsWith('0X')) { if (str.startsWith('0x') || str.startsWith('0X')) {
if (!/^0x[0-9a-f]+$/i.test(str)) throw new Error(`无效的大小/偏移值: "${str}"`); if (!/^0x[0-9a-f]+$/i.test(str)) throw new Error(`Invalid size/offset value: "${str}"`);
const v = parseInt(str, 16); const v = parseInt(str, 16);
if (isNaN(v) || v < 0 || v > U32_MAX) throw new Error(`无效的大小/偏移值: "${str}"`); if (isNaN(v) || v < 0 || v > U32_MAX) throw new Error(`Invalid size/offset value: "${str}"`);
return v; return v;
} }
@ -40,11 +40,11 @@ function parseSize(str: string): number {
if (unit === 'K') result = Math.floor(num * 1024); if (unit === 'K') result = Math.floor(num * 1024);
else if (unit === 'M') result = Math.floor(num * 1024 * 1024); else if (unit === 'M') result = Math.floor(num * 1024 * 1024);
else result = Math.floor(num); else result = Math.floor(num);
if (result > U32_MAX) throw new Error(`无效的大小/偏移值: "${str}" (超出 32 位范围)`); if (result > U32_MAX) throw new Error(`Invalid size/offset value: "${str}" (exceeds 32-bit range)`);
return result; return result;
} }
throw new Error(`无效的大小/偏移值: "${str}"`); throw new Error(`Invalid size/offset value: "${str}"`);
} }
/** /**
@ -86,7 +86,7 @@ function splitCsvLine(line: string): string[] {
} }
} }
} }
if (inQuotes) throw new Error('引号未闭合'); if (inQuotes) throw new Error('Unclosed quote');
fields.push(current.trim()); fields.push(current.trim());
return fields; return fields;
} }
@ -111,7 +111,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin
try { try {
const fields = splitCsvLine(line); const fields = splitCsvLine(line);
if (fields.length < 5) { if (fields.length < 5) {
onWarning?.(lineIdx + 1, `字段数量不足 (需要 5实际 ${fields.length}): "${line}"`); onWarning?.(lineIdx + 1, `Not enough fields (need 5, got ${fields.length}): "${line}"`);
continue; continue;
} }
@ -127,7 +127,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin
const type = NAME_TO_TYPE[typeName]; const type = NAME_TO_TYPE[typeName];
if (type === undefined) { if (type === undefined) {
onWarning?.(lineIdx + 1, `未知分区类型: "${typeName}"`); onWarning?.(lineIdx + 1, `Unknown partition type: "${typeName}"`);
continue; continue;
} }
@ -137,7 +137,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin
const flags = parseFlags(flagsStr); const flags = parseFlags(flagsStr);
entries.push({ name, type, subtype, offset, size, flags }); entries.push({ name, type, subtype, offset, size, flags });
} catch (e) { } catch (e) {
onWarning?.(lineIdx + 1, `解析失败: ${(e as Error).message}`); onWarning?.(lineIdx + 1, `Parse failed: ${(e as Error).message}`);
} }
} }

View File

@ -7,12 +7,12 @@ const U32_MAX = 0xFFFF_FFFF;
function assertU8(val: number, field: string): void { function assertU8(val: number, field: string): void {
if (!Number.isInteger(val) || val < 0 || val > 0xFF) if (!Number.isInteger(val) || val < 0 || val > 0xFF)
throw new Error(`"${field}" 不是有效的字节值 (0255): ${val}`); throw new Error(`"${field}" is not a valid byte value (0255): ${val}`);
} }
function assertU32(val: number, field: string): void { function assertU32(val: number, field: string): void {
if (!Number.isInteger(val) || val < 0 || val > U32_MAX) if (!Number.isInteger(val) || val < 0 || val > U32_MAX)
throw new Error(`"${field}" 不是有效的 32 位无符号整数 (00xFFFFFFFF): ${val}`); throw new Error(`"${field}" is not a valid uint32 (00xFFFFFFFF): ${val}`);
} }
/** /**
@ -26,14 +26,14 @@ export function serializeBinary(table: PartitionTable): Uint8Array {
let offset = 0; let offset = 0;
for (const entry of table.entries) { for (const entry of table.entries) {
if (offset + ENTRY_SIZE > TABLE_MAX_SIZE - ENTRY_SIZE) { if (offset + ENTRY_SIZE > TABLE_MAX_SIZE - ENTRY_SIZE) {
throw new Error(`分区表条目过多,超过最大容量 (最多 ${Math.floor((TABLE_MAX_SIZE - ENTRY_SIZE) / ENTRY_SIZE)})`); throw new Error(`Too many partition table entries (max ${Math.floor((TABLE_MAX_SIZE - ENTRY_SIZE) / ENTRY_SIZE)})`);
} }
assertU8(entry.type, '类型'); assertU8(entry.type, 'type');
assertU8(entry.subtype, '子类型'); assertU8(entry.subtype, 'subtype');
assertU32(entry.offset, '偏移量'); assertU32(entry.offset, 'offset');
assertU32(entry.size, '大小'); assertU32(entry.size, 'size');
assertU32(entry.flags, '标志'); assertU32(entry.flags, 'flags');
writeU16(buf, offset, ENTRY_MAGIC); writeU16(buf, offset, ENTRY_MAGIC);
buf[offset + 2] = entry.type; buf[offset + 2] = entry.type;

View File

@ -19,7 +19,7 @@ export function validateTable(table: PartitionTable): PartitionValidationError[]
if (names.has(entry.name)) { if (names.has(entry.name)) {
errors.push({ errors.push({
type: 'duplicate_name', type: 'duplicate_name',
message: `分区名称重复: "${entry.name}"`, message: `Duplicate partition name: "${entry.name}"`,
entryA: names.get(entry.name), entryA: names.get(entry.name),
entryB: entry, entryB: entry,
}); });
@ -33,14 +33,14 @@ export function validateTable(table: PartitionTable): PartitionValidationError[]
if (entry.offset !== 0 && entry.offset % SECTOR_SIZE !== 0) { if (entry.offset !== 0 && entry.offset % SECTOR_SIZE !== 0) {
errors.push({ errors.push({
type: 'alignment', type: 'alignment',
message: `"${entry.name}" 偏移 0x${entry.offset.toString(16)} 未对齐到 4KB 边界`, message: `"${entry.name}" offset 0x${entry.offset.toString(16)} not aligned to 4KB boundary`,
entryA: entry, entryA: entry,
}); });
} }
if (entry.size !== 0 && entry.size % SECTOR_SIZE !== 0) { if (entry.size !== 0 && entry.size % SECTOR_SIZE !== 0) {
errors.push({ errors.push({
type: 'alignment', type: 'alignment',
message: `"${entry.name}" 大小 0x${entry.size.toString(16)} 未对齐到 4KB 边界`, message: `"${entry.name}" size 0x${entry.size.toString(16)} not aligned to 4KB boundary`,
entryA: entry, entryA: entry,
}); });
} }
@ -57,7 +57,7 @@ export function validateTable(table: PartitionTable): PartitionValidationError[]
if (a.offset < bEnd && b.offset < aEnd) { if (a.offset < bEnd && b.offset < aEnd) {
errors.push({ errors.push({
type: 'overlap', type: 'overlap',
message: `"${a.name}" [0x${a.offset.toString(16)}..0x${aEnd.toString(16)}] "${b.name}" [0x${b.offset.toString(16)}..0x${bEnd.toString(16)}] 重叠`, message: `"${a.name}" [0x${a.offset.toString(16)}..0x${aEnd.toString(16)}] overlaps "${b.name}" [0x${b.offset.toString(16)}..0x${bEnd.toString(16)}]`,
entryA: a, entryA: a,
entryB: b, entryB: b,
}); });