spellKoreanNumber function
Spells one comma-grouped decimal integer using pinned g2pkc number rules.
Upstream's Unicode \d matcher accepts every Unicode decimal digit, while
its digit-name tables intentionally contain ASCII keys only. That
observable mismatch is preserved here.
Pinned g2pkc defines positions 0 through 15. Input containing more than 16 comma-free digits throws InvalidConfigurationException, preserving its no-output failure boundary with an actionable typed package error. Empty input, non-decimal characters, and misplaced non-comma separators throw the same exception.
Implementation
String spellKoreanNumber(String number, {bool sino = true}) {
final digitsOnly = number.replaceAll(',', '');
final digits = <String>[
for (final scalar in digitsOnly.runes) String.fromCharCode(scalar),
];
if (digits.isEmpty ||
digitsOnly.runes.any((scalar) => !isPython312DecimalScalar(scalar))) {
throw const InvalidConfigurationException(
'A Korean number must contain at least one Unicode decimal digit and '
'otherwise only commas.',
);
}
if (digits.length > 16) {
throw InvalidConfigurationException(
'Pinned g2pkc leaves Korean numeral positions above 15 undefined; '
'received ${digits.length} comma-free digits.',
);
}
if (digitsOnly == '0') {
return '영';
}
if (!sino && digitsOnly == '20') {
return '스무';
}
final output = <String>[];
for (var index = 0; index < digits.length; index++) {
final digit = digits[index];
final position = digits.length - index - 1;
var name = '';
if (sino || digits.length >= 3) {
if (position == 0) {
name = _sinoDigitNames[digit] ?? '';
} else if (position == 1) {
name = '${_sinoDigitNames[digit] ?? ''}십'.replaceFirst('일십', '십');
}
} else if (position == 0) {
name = _nativeModifiers[digit] ?? '';
} else if (position == 1) {
name = _nativeTens[digit] ?? '';
}
if (digit == '0') {
if (position % 4 == 0) {
final start = output.length < 3 ? 0 : output.length - 3;
if (output.sublist(start).join().isEmpty) {
output.add('');
continue;
}
} else {
output.add('');
continue;
}
}
if (position == 2) {
name = '${_sinoDigitNames[digit] ?? ''}백'.replaceFirst('일백', '백');
} else if (position == 3) {
name = '${_sinoDigitNames[digit] ?? ''}천'.replaceFirst('일천', '천');
} else if (position == 4) {
name = '${_sinoDigitNames[digit] ?? ''}만'.replaceFirst('일만', '만');
} else if (position == 5) {
name = '${_sinoDigitNames[digit] ?? ''}십'.replaceFirst('일십', '십');
} else if (position == 6) {
name = '${_sinoDigitNames[digit] ?? ''}백'.replaceFirst('일백', '백');
} else if (position == 7) {
name = '${_sinoDigitNames[digit] ?? ''}천'.replaceFirst('일천', '천');
} else if (position == 8) {
name = '${_sinoDigitNames[digit] ?? ''}억';
} else if (position == 9) {
name = '${_sinoDigitNames[digit] ?? ''}십';
} else if (position == 10) {
name = '${_sinoDigitNames[digit] ?? ''}백';
} else if (position == 11) {
name = '${_sinoDigitNames[digit] ?? ''}천';
} else if (position == 12) {
name = '${_sinoDigitNames[digit] ?? ''}조';
} else if (position == 13) {
name = '${_sinoDigitNames[digit] ?? ''}십';
} else if (position == 14) {
name = '${_sinoDigitNames[digit] ?? ''}백';
} else if (position == 15) {
name = '${_sinoDigitNames[digit] ?? ''}천';
}
output.add(name);
}
return output.join();
}