Skip to content

Commit

Permalink
feat: basic functions
Browse files Browse the repository at this point in the history
  • Loading branch information
EqualMa committed Aug 14, 2020
1 parent d92842c commit 9b2c432
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./to-chinese";
export * from "./options";
export * from "./to-ordinal";
81 changes: 81 additions & 0 deletions src/label/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as labels from "./labels";
import { NumberToChineseOptionsObject } from "../options";

export interface NumberToChineseLabels {
digits: [
// 零
string,
// 一
string,
// 二
string,
// 三
string,
// 四
string,
// 五
string,
// 六
string,
// 七
string,
// 八
string,
// 九
string,
];
units: [
"",
// 十
string,
// 百
string,
// 千
string,
// 万
string,
// 十
string,
// 百
string,
// 千
string,
// 亿
string,
// 十
string,
// 百
string,
// 千
string,
// 兆
string,
// 十
string,
// 百
string,
// 千
string,
// 京
string,
// 十
string,
// 百
string,
// 千
string,
// 垓
string,
];
ordinal: string; // 第
point: string; // 点
minus: string; // 负
}

export * from "./labels";

export function getLabels(
opts: NumberToChineseOptionsObject,
): NumberToChineseLabels {
return labels[opts.chineseType];
}
2 changes: 2 additions & 0 deletions src/label/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as simplified } from "./simplified";
export { default as traditional } from "./traditional";
33 changes: 33 additions & 0 deletions src/label/simplified.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { NumberToChineseLabels } from ".";

const labels: NumberToChineseLabels = {
digits: ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"],
units: [
"",
"十",
"百",
"千",
"万",
"十",
"百",
"千",
"亿",
"十",
"百",
"千",
"兆",
"十",
"百",
"千",
"京",
"十",
"百",
"千",
"垓",
],
ordinal: "第",
point: "点",
minus: "负",
};

export default labels;
33 changes: 33 additions & 0 deletions src/label/traditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { NumberToChineseLabels } from ".";

const labels: NumberToChineseLabels = {
digits: ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"],
units: [
"",
"十",
"百",
"千",
"萬",
"十",
"百",
"千",
"億",
"十",
"百",
"千",
"兆",
"十",
"百",
"千",
"京",
"十",
"百",
"千",
"垓",
],
ordinal: "第",
point: "點",
minus: "負",
};

export default labels;
59 changes: 59 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export type ChineseType = "simplified" | "traditional";
export interface NumberToChineseOptionsObject {
chineseType: ChineseType;
keepOne: boolean;
}

export type ShortChineseType = "s" | "t";

export type NumberToChineseOptions =
| ShortChineseType
| ChineseType
| Partial<NumberToChineseOptionsObject>;

const DEFAULT_OPTIONS: NumberToChineseOptionsObject = Object.freeze({
chineseType: "simplified",
keepOne: false,
});

export function normalizeChineseType(
type: ShortChineseType | ChineseType,
): ChineseType {
switch (type) {
case "s":
return "simplified";
case "t":
return "traditional";
default:
return type;
}
}

export function normalizeOptions(
opts?: NumberToChineseOptions,
): NumberToChineseOptionsObject {
if (typeof opts === "boolean") {
return {
chineseType: opts ? "traditional" : "simplified",
keepOne: DEFAULT_OPTIONS.keepOne,
};
} else if (typeof opts === "string") {
return {
chineseType: normalizeChineseType(opts),
keepOne: DEFAULT_OPTIONS.keepOne,
};
} else if (typeof opts === "object" && opts !== null) {
return {
chineseType:
typeof opts.chineseType === "string"
? normalizeChineseType(opts.chineseType)
: DEFAULT_OPTIONS.chineseType,
keepOne:
typeof opts.keepOne === "undefined"
? DEFAULT_OPTIONS.keepOne
: !!opts.keepOne,
};
} else {
return DEFAULT_OPTIONS;
}
}
112 changes: 112 additions & 0 deletions src/to-chinese.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { NumberToChineseOptions, normalizeOptions } from "./options";
import { getLabels, NumberToChineseLabels } from "./label";

interface LabelRegexps {
onlyZero: [RegExp, string];
zeroBetweenBigUnits: [RegExp, string];
zeroBetweenSmallAndBigUnits: [RegExp, string];
groupZero: [RegExp, string];
removeTailZero: [RegExp, string];
removeOne: [RegExp, string];
}

const regexps = new WeakMap<NumberToChineseLabels, LabelRegexps>();

function getRegexps(labels: NumberToChineseLabels): LabelRegexps {
const r = regexps.get(labels);

if (r) {
return r;
}

const { digits, units } = labels;
const smallUnit = units[1] + units[2] + units[3];
const bigUnit = units[4] + units[8] + units[12] + units[16] + units[20];
const zero = digits[0];

const v: LabelRegexps = {
// 零千,零百,零十 keeps 零
onlyZero: [new RegExp(`(${zero})[${smallUnit}]`, "g"), "$1"],
//大數中間沒細數,補零
zeroBetweenBigUnits: [
new RegExp(`([${bigUnit}])[^${smallUnit}]+([${bigUnit}])`, "g"),
"$1" + zero,
],
zeroBetweenSmallAndBigUnits: [
new RegExp(`([${smallUnit}])${zero}+([${bigUnit}])`, "g"),
"$1$2" + zero,
],
//group 零
groupZero: [new RegExp(`(${digits[0]})+`, "g"), "$1"],
// remove tail zero
removeTailZero: [new RegExp(zero + "+$"), ""],
// 一十 => 十
removeOne: [new RegExp(`^${digits[1]}${units[1]}`), units[1]],
};
regexps.set(labels, v);

return v;
}

function parseIntPart(
num: number,
labels: NumberToChineseLabels,
keepOne: boolean,
) {
const { digits, units, minus } = labels;

const regexps = getRegexps(labels);

let n = Math.floor(Math.abs(num));

// 0
if (n < 1) return (num < 0 ? minus : "") + digits[0];

let str = "";
let unitIndex = 0;
while (n > 0) {
str = digits[n % 10] + units[unitIndex] + str;

n = Math.floor(n / 10);
unitIndex++;
}

str = str
.replace(...regexps.onlyZero)
.replace(...regexps.zeroBetweenBigUnits)
.replace(...regexps.zeroBetweenSmallAndBigUnits)
.replace(...regexps.groupZero)
.replace(...regexps.removeTailZero);

if (keepOne != true) {
str = str.replace(...regexps.removeOne);
}

return (num < 0 ? minus : "") + str;
}

function parseFloatPart(num: number, labels: NumberToChineseLabels): string {
if (num % 1 === 0) return "";

num = Math.abs(num);
const { digits, point } = labels;

let numStr = num.toString();
numStr = numStr.slice(numStr.indexOf(".") + 1);

let str = "";
for (const d of numStr) {
str = str + digits[parseInt(d)];
}

return point + str;
}

export function numberToChinese(
num: number,
options?: NumberToChineseOptions,
): string {
const opts = normalizeOptions(options);
const labels = getLabels(opts);
return parseIntPart(num, labels, opts.keepOne) + parseFloatPart(num, labels);
}
18 changes: 18 additions & 0 deletions src/to-ordinal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NumberToChineseOptions, normalizeOptions } from "./options";
import { getLabels } from "./label";
import { numberToChinese } from "./to-chinese";

export function numberToChineseOrdinalWithArabic(
num: number,
options?: NumberToChineseOptions,
): string {
return getLabels(normalizeOptions(options)).ordinal + num.toString(10);
}

export function numberToChineseOrdinal(
num: number,
options?: NumberToChineseOptions,
): string {
const opts = normalizeOptions(options);
return getLabels(opts).ordinal + numberToChinese(num, opts);
}

0 comments on commit 9b2c432

Please sign in to comment.