diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 6d96350e..1bba496f 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -48,3 +48,30 @@
### 2.2 Description
A clear and concise description of what the pr is about.
+
+## 3. Convention
+
+함수명에는 특별한 이유가 없다면 hangul을 포함하지 않습니다.
+
+```ts
+// Don't
+function getHangulSimilarity();
+// Do
+function getSimilarity();
+
+// Don't
+function disassembleHangul();
+// Do
+function disassemble();
+```
+
+함수명을 지을 때 아래와 같이 import될 것을 고려해야 합니다.
+
+```ts
+import { getSimilarity, disassemble, josa } from 'es-hangul' // 따로 나눠서도 제공
+import hangul from 'es-hangul' // hangul default export에 묶어서도 제공
+
+hangul.getSimilarity(...)
+hangul.disassemble(...)
+hangul.josa(...)
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d921fbd..2a30ae47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,41 @@
# es-hangul
+## 1.4.1
+
+### Patch Changes
+
+- [#157](https://github.com/toss/es-hangul/pull/157) [`f7e60ae`](https://github.com/toss/es-hangul/commit/f7e60aeca9f315ac1e34eba0a1f8a82f55d79956) Thanks [@manudeli](https://github.com/manudeli)! - fix: 패키지가 노출하는 인터페이스를 명확히 하기 위해 index.ts를 named export로 수정합니다
+
+## 1.4.0
+
+### Minor Changes
+
+- [#144](https://github.com/toss/es-hangul/pull/144) [`b114897`](https://github.com/toss/es-hangul/commit/b1148973e6c2b640ce528fc8ba4b8e2e034b90de) Thanks [@Collection50](https://github.com/Collection50)! - fix: amountToHangul이 소수점, 숫자도 대응할 수 있도록 수정
+
+## 1.3.10
+
+### Patch Changes
+
+- [#148](https://github.com/toss/es-hangul/pull/148) [`f3c7fe9`](https://github.com/toss/es-hangul/commit/f3c7fe9f73138b932af817b8ac925d54c3283151) Thanks [@KNU-K](https://github.com/KNU-K)! - fix : getHangulacronym함수를 acronymizeHangul 메서드로 대체합니다
+
+## 1.3.9
+
+### Patch Changes
+
+- [#130](https://github.com/toss/es-hangul/pull/130) [`acd6edb`](https://github.com/toss/es-hangul/commit/acd6edb1d8aadced517f6b57a49c01152ff19d0a) Thanks [@Collection50](https://github.com/Collection50)! - feat: 문자열에서 한글만 반환하는 extractHangul을 구현합니다.
+
+## 1.3.8
+
+### Patch Changes
+
+- [#133](https://github.com/toss/es-hangul/pull/133) [`b0e1184`](https://github.com/toss/es-hangul/commit/b0e1184204be0cb9f3c13937888c83c8a94e7ca6) Thanks [@KNU-K](https://github.com/KNU-K)! - feat : 문장의 각 단어 중 첫 문자만 뽑는 extractHangul 함수를 추가합니다.
+
+## 1.3.7
+
+### Patch Changes
+
+- [#124](https://github.com/toss/es-hangul/pull/124) [`0f38431`](https://github.com/toss/es-hangul/commit/0f38431ee611cb89c7e121fd02ab34f749a0c386) Thanks [@crucifyer](https://github.com/crucifyer)! - fix: 코드 효율 개선
+
## 1.3.6
### Patch Changes
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index e2fbde80..8009477c 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,47 @@
# docs
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies [[`f7e60ae`](https://github.com/toss/es-hangul/commit/f7e60aeca9f315ac1e34eba0a1f8a82f55d79956)]:
+ - es-hangul@1.4.1
+
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies [[`b114897`](https://github.com/toss/es-hangul/commit/b1148973e6c2b640ce528fc8ba4b8e2e034b90de)]:
+ - es-hangul@1.4.0
+
+## 0.1.6
+
+### Patch Changes
+
+- Updated dependencies [[`f3c7fe9`](https://github.com/toss/es-hangul/commit/f3c7fe9f73138b932af817b8ac925d54c3283151)]:
+ - es-hangul@1.3.10
+
+## 0.1.5
+
+### Patch Changes
+
+- Updated dependencies [[`acd6edb`](https://github.com/toss/es-hangul/commit/acd6edb1d8aadced517f6b57a49c01152ff19d0a)]:
+ - es-hangul@1.3.9
+
+## 0.1.4
+
+### Patch Changes
+
+- Updated dependencies [[`b0e1184`](https://github.com/toss/es-hangul/commit/b0e1184204be0cb9f3c13937888c83c8a94e7ca6)]:
+ - es-hangul@1.3.8
+
+## 0.1.3
+
+### Patch Changes
+
+- Updated dependencies [[`0f38431`](https://github.com/toss/es-hangul/commit/0f38431ee611cb89c7e121fd02ab34f749a0c386)]:
+ - es-hangul@1.3.7
+
## 0.1.2
### Patch Changes
diff --git a/docs/package.json b/docs/package.json
index 2b0bc353..9269fa8e 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "docs",
- "version": "0.1.2",
+ "version": "0.1.8",
"private": true,
"packageManager": "yarn@4.1.1",
"scripts": {
diff --git a/docs/public/favicon-dark.png b/docs/public/favicon-dark.png
new file mode 100644
index 00000000..07608185
Binary files /dev/null and b/docs/public/favicon-dark.png differ
diff --git a/docs/public/favicon-dark.svg b/docs/public/favicon-dark.svg
new file mode 100644
index 00000000..c5c553b4
--- /dev/null
+++ b/docs/public/favicon-dark.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico
deleted file mode 100644
index 718d6fea..00000000
Binary files a/docs/public/favicon.ico and /dev/null differ
diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg
new file mode 100644
index 00000000..e6aa5e00
--- /dev/null
+++ b/docs/public/favicon.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/docs/public/next.svg b/docs/public/next.svg
deleted file mode 100644
index 5174b28c..00000000
--- a/docs/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/public/vercel.svg b/docs/public/vercel.svg
deleted file mode 100644
index d2f84222..00000000
--- a/docs/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/src/pages/docs/api/acronymizeHangul.en.mdx b/docs/src/pages/docs/api/acronymizeHangul.en.mdx
new file mode 100644
index 00000000..57d026c9
--- /dev/null
+++ b/docs/src/pages/docs/api/acronymizeHangul.en.mdx
@@ -0,0 +1,16 @@
+# acronymizeHangul
+
+It receives the Korean sentence and returns the first letter of that Korean sentence.
+(We don't deal with non-Korean sentences; we don't deal with additional Korean + English sentences.)
+
+```typescript
+function acronymizeHangul(
+ // String consisting of plural nouns (e.g. '버스 충전', '치킨과 맥주')
+ hangul: string
+): boolean;
+```
+
+```typescript
+acronymizeHangul('치킨과 맥주').join(''); //치맥
+acronymizeHangul('버스 충전 카드').join(''); //버충카
+```
diff --git a/docs/src/pages/docs/api/acronymizeHangul.ko.mdx b/docs/src/pages/docs/api/acronymizeHangul.ko.mdx
new file mode 100644
index 00000000..908e5251
--- /dev/null
+++ b/docs/src/pages/docs/api/acronymizeHangul.ko.mdx
@@ -0,0 +1,16 @@
+# acronymizeHangul
+
+한글 문장을 입력받아서, 해당 한글 문장의 첫글자를 리턴해줍니다.
+(한글 문장이 아닌, 문장은 취급하지않습니다. 추가로 한글 문장 + 영어 문장의 경우에도 취급하지않습니다.)
+
+```typescript
+function acronymizeHangul(
+ // 복수 명사로 이루어진 문자열 (e.g. '버스 충전', '치킨과 맥주')
+ hangul: string
+): boolean;
+```
+
+```typescript
+acronymizeHangul('치킨과 맥주').join(''); //치맥
+acronymizeHangul('버스 충전 카드').join(''); //버충카
+```
diff --git a/docs/src/pages/docs/api/amountToHangul.en.md b/docs/src/pages/docs/api/amountToHangul.en.md
index c82c4dc4..19e3abcc 100644
--- a/docs/src/pages/docs/api/amountToHangul.en.md
+++ b/docs/src/pages/docs/api/amountToHangul.en.md
@@ -9,16 +9,14 @@ Converts numeric amounts to the Korean reading of the [National Institute of Kor
For detailed examples, see below.
```typescript
-function amountToHangul(
- // A string of numeric amounts
- str: string
-): string;
+function amountToHangul(amount: string | number): string;
```
## Examples
```tsx
-amountToHangul('15,201,100'); // '일천오백이십만천백';
-amountToHangul('120,030원'); // '일십이만삼십' - Ignore non-numeric characters
-amountToHangul('392.24'); // '삼백구십이' - Ignore decimals
+amountToHangul('15,201,100'); // '일천오백이십만천백'
+amountToHangul('120,030원'); // '일십이만삼십'
+amountToHangul('12345.6789'); // '일만이천삼백사십오점육칠팔구'
+amountToHangul(15_201_100); // '일천오백이십만천백''
```
diff --git a/docs/src/pages/docs/api/amountToHangul.ko.md b/docs/src/pages/docs/api/amountToHangul.ko.md
index 9818d26d..dbafc07e 100644
--- a/docs/src/pages/docs/api/amountToHangul.ko.md
+++ b/docs/src/pages/docs/api/amountToHangul.ko.md
@@ -4,21 +4,19 @@ title: amountToHangul
# amountToHangul
-숫자로 된 금액을 [국립국어원](https://ko.dict.naver.com/#/correct/korean/info?seq=602) 규칙의 한글 읽기로 변환합니다.
+숫자나 문자를 [국립국어원](https://ko.dict.naver.com/#/correct/korean/info?seq=602) 규칙의 한글 읽기 문자열로 변환합니다.
자세한 예시는 아래 Example을 참고하세요.
```typescript
-function amountToHangul(
- // 숫자로 된 금액 문자열
- str: string
-): string;
+function amountToHangul(amount: string | number): string;
```
## Examples
```tsx
-amountToHangul('15,201,100'); // '일천오백이십만천백';
-amountToHangul('120,030원'); // '일십이만삼십' - 숫자 외 문자 무시
-amountToHangul('392.24'); // '삼백구십이' - 소수점 무시
+amountToHangul('15,201,100'); // '일천오백이십만천백'
+amountToHangul('120,030원'); // '일십이만삼십'
+amountToHangul('12345.6789'); // '일만이천삼백사십오점육칠팔구'
+amountToHangul(15_201_100); // '일천오백이십만천백''
```
diff --git a/docs/src/pages/docs/api/extractHangul.en.md b/docs/src/pages/docs/api/extractHangul.en.md
new file mode 100644
index 00000000..c685c84a
--- /dev/null
+++ b/docs/src/pages/docs/api/extractHangul.en.md
@@ -0,0 +1,23 @@
+---
+title: extractHangul
+---
+
+# extractHangul
+
+Extracts and returns only Korean characters from the string.
+
+For detailed examples, see below.
+
+```typescript
+function extractHangul(str: string): string;
+```
+
+## Examples
+
+```tsx
+extractHangul('안녕하세요1234abc'); // '안녕하세요'
+extractHangul('abcde'); // ''
+extractHangul('안녕하세요ㄱㄴ'); // '안녕하세요ㄱㄴ'
+extractHangul('안녕하세요 만나서 반갑습니다'); // '안녕하세요 만나서 반갑습니다'
+extractHangul('가나다!-29~라마바.,,사'); // '가나다라마바사'
+```
diff --git a/docs/src/pages/docs/api/extractHangul.ko.md b/docs/src/pages/docs/api/extractHangul.ko.md
new file mode 100644
index 00000000..dc089bdb
--- /dev/null
+++ b/docs/src/pages/docs/api/extractHangul.ko.md
@@ -0,0 +1,23 @@
+---
+title: extractHangul
+---
+
+# extractHangul
+
+문자열에서 한글만 추출하여 반환합니다.
+
+자세한 예시는 아래 Example을 참고하세요.
+
+```typescript
+function extractHangul(str: string): string;
+```
+
+## Examples
+
+```tsx
+extractHangul('안녕하세요1234abc'); // '안녕하세요'
+extractHangul('abcde'); // ''
+extractHangul('안녕하세요ㄱㄴ'); // '안녕하세요ㄱㄴ'
+extractHangul('안녕하세요 만나서 반갑습니다'); // '안녕하세요 만나서 반갑습니다'
+extractHangul('가나다!-29~라마바.,,사'); // '가나다라마바사'
+```
diff --git a/docs/src/pages/docs/api/josa.ko.md b/docs/src/pages/docs/api/josa.ko.md
index 91adb784..bb094723 100644
--- a/docs/src/pages/docs/api/josa.ko.md
+++ b/docs/src/pages/docs/api/josa.ko.md
@@ -18,7 +18,6 @@ function josa(
| '으로/로'
| '와/과'
| '이나/나'
- | '이에/에'
| '이란/란'
| '아/야'
| '이랑/랑'
diff --git a/package.json b/package.json
index 3888a6dd..9f2f69e8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "es-hangul",
- "version": "1.3.6",
+ "version": "1.4.1",
"keywords": [
"한글",
"한국어",
diff --git a/src/_internal/hangul.spec.ts b/src/_internal/hangul.spec.ts
index 7cf63bbe..e5307cf0 100644
--- a/src/_internal/hangul.spec.ts
+++ b/src/_internal/hangul.spec.ts
@@ -1,4 +1,13 @@
-import { binaryAssembleHangulCharacters, binaryAssembleHangul, isHangulAlphabet, isHangulCharacter } from './hangul';
+import {
+ binaryAssembleHangulCharacters,
+ binaryAssembleHangul,
+ isHangulAlphabet,
+ isHangulCharacter,
+ isHangul,
+ assertHangul,
+ parseHangul,
+ safeParseHangul,
+} from './hangul';
describe('isHangul*', () => {
it('isHangulCharacter는 완성된 한글 문자를 받으면 true를 반환한다.', () => {
@@ -16,6 +25,51 @@ describe('isHangul*', () => {
expect(isHangulAlphabet('ㅏ')).toBe(true);
expect(isHangulAlphabet('a')).toBe(false);
});
+
+ it('isHangul은 한글 문자열을 받으면 true를 반환한다.', () => {
+ expect(isHangul('값')).toBe(true);
+ expect(isHangul('ㄱ')).toBe(true);
+ expect(isHangul('ㅏ')).toBe(true);
+ expect(isHangul('저는 고양이를 좋아합니다')).toBe(true);
+ expect(isHangul('a')).toBe(false);
+ expect(isHangul(111)).toBe(false);
+ expect(isHangul([111, 111])).toBe(false);
+ expect(isHangul({ a: 111 })).toBe(false);
+ });
+});
+
+describe('parse', () => {
+ it('parseHangul은 한글 문자열을 받으면 그대로 반환한다.', () => {
+ expect(parseHangul('값')).toBe('값');
+ expect(parseHangul('ㄱ')).toBe('ㄱ');
+ expect(parseHangul('ㅏ')).toBe('ㅏ');
+ expect(parseHangul('저는 고양이를 좋아합니다')).toBe('저는 고양이를 좋아합니다');
+ });
+
+ it('parseHangul은 한글 문자열이 아닌 값을 받으면 에러를 발생시킨다.', () => {
+ expect(() => parseHangul(111)).toThrowError('111 is not a valid hangul string');
+ expect(() => parseHangul([111, 111])).toThrowError('[111,111] is not a valid hangul string');
+ expect(() => parseHangul({ a: 111 })).toThrowError('{"a":111} is not a valid hangul string');
+ });
+
+ it('safeParseHangul은 한글 문자열을 받으면 성공 객체를 반환한다.', () => {
+ expect(safeParseHangul('값')).toEqual({ success: true, data: '값' });
+ expect(safeParseHangul('ㄱ')).toEqual({ success: true, data: 'ㄱ' });
+ expect(safeParseHangul('ㅏ')).toEqual({ success: true, data: 'ㅏ' });
+ expect(safeParseHangul('저는 고양이를 좋아합니다')).toEqual({ success: true, data: '저는 고양이를 좋아합니다' });
+ });
+
+ it('safeParseHangul은 한글 문자열이 아닌 값을 받으면 실패 객체를 반환한다.', () => {
+ expect(safeParseHangul(111)).toEqual({ success: false, error: Error('111 is not a valid hangul string') });
+ expect(safeParseHangul([111, 111])).toEqual({
+ success: false,
+ error: Error('[111,111] is not a valid hangul string'),
+ });
+ expect(safeParseHangul({ a: 111 })).toEqual({
+ success: false,
+ error: Error('{"a":111} is not a valid hangul string'),
+ });
+ });
});
describe('binaryAssembleHangulCharacters', () => {
@@ -107,4 +161,20 @@ describe('binaryAssembleHangul', () => {
expect(binaryAssembleHangul('저는 고양이를 좋아하', 'ㅏ')).toEqual('저는 고양이를 좋아하ㅏ');
expect(binaryAssembleHangul('저는 고양이를 좋아합니다', 'ㅜ')).toEqual('저는 고양이를 좋아합니다ㅜ');
});
+
+ describe('assertHangul', () => {
+ it('한글 문자열을 받으면 에러를 발생시키지 않는다.', () => {
+ expect(() => assertHangul('ㄱ')).not.toThrow();
+ expect(() => assertHangul('고양이')).not.toThrow();
+ expect(() => assertHangul('저는 고양이를 좋아합니다')).not.toThrow();
+ expect(() => assertHangul('저는 고양이를 좋아합니ㄷ')).not.toThrow();
+ });
+
+ it("한글 문자열이 아닌 값을 받으면 '___ is not a valid hangul string' 에러를 발생시킨다.", () => {
+ expect(() => assertHangul('aaaaaa')).toThrowError('"aaaaaa" is not a valid hangul string');
+ expect(() => assertHangul(111)).toThrowError('111 is not a valid hangul string');
+ expect(() => assertHangul([111, 111])).toThrowError('[111,111] is not a valid hangul string');
+ expect(() => assertHangul({ a: 111 })).toThrowError('{"a":111} is not a valid hangul string');
+ });
+ });
});
diff --git a/src/_internal/hangul.ts b/src/_internal/hangul.ts
index 6a1479ca..1a746f5b 100644
--- a/src/_internal/hangul.ts
+++ b/src/_internal/hangul.ts
@@ -12,6 +12,40 @@ export function isHangulAlphabet(character: string) {
return /^[ㄱ-ㅣ]$/.test(character);
}
+export function isHangul(actual: unknown): actual is string {
+ return typeof actual === 'string' && /^[가-힣ㄱ-ㅣ\s]+$/.test(actual);
+}
+
+export function assertHangul(actual: unknown, message?: string): asserts actual is string {
+ assert(isHangul(actual), message || `${JSON.stringify(actual)} is not a valid hangul string`);
+}
+
+export function parseHangul(actual: unknown): string {
+ assertHangul(actual);
+ return actual;
+}
+
+type SafeParseSuccess = {
+ success: true;
+ data: string;
+ error?: never;
+};
+
+type SafeParseError = {
+ success: false;
+ error: unknown;
+ data?: never;
+};
+
+export function safeParseHangul(actual: unknown): SafeParseSuccess | SafeParseError {
+ try {
+ const parsedHangul = parseHangul(actual);
+ return { success: true, data: parsedHangul };
+ } catch (error) {
+ return { success: false, error };
+ }
+}
+
/**
* @name binaryAssembleHangulAlphabets
* @description
diff --git a/src/_internal/index.spec.ts b/src/_internal/index.spec.ts
new file mode 100644
index 00000000..6c6bc393
--- /dev/null
+++ b/src/_internal/index.spec.ts
@@ -0,0 +1,61 @@
+import assert, { excludeLastElement, isBlank, joinString } from './index';
+
+describe('excludeLastElement', () => {
+ it('마지막 요소를 제외한 모든 요소와 마지막 요소를 반환한다', () => {
+ const result = excludeLastElement(['apple', 'banana', 'cherry']);
+
+ expect(result).toEqual([['apple', 'banana'], 'cherry']);
+ });
+
+ it('입력 배열이 비어 있으면 빈 배열과 빈 문자열을 반환한다', () => {
+ const result = excludeLastElement([]);
+
+ expect(result).toEqual([[], '']);
+ });
+
+ it('배열에 단 하나의 요소만 있는 경우, 빈배열과 그 요소를 반환한다', () => {
+ const result = excludeLastElement(['apple']);
+
+ expect(result).toEqual([[], 'apple']);
+ });
+});
+
+describe('joinString', () => {
+ it('여러 문자열을 하나의 문자열로 연결한다', () => {
+ const result = joinString('Hello', ' ', 'World');
+
+ expect(result).toBe('Hello World');
+ });
+
+ it('인자가 주어지지 않았을 때 빈 문자열을 반환한다', () => {
+ const result = joinString();
+
+ expect(result).toBe('');
+ });
+});
+
+describe('isBlank', () => {
+ it('문자가 공백이면 true를 반환한다', () => {
+ expect(isBlank(' ')).toBe(true);
+ });
+
+ it('문자가 공백이 아니면 false를 반환한다', () => {
+ expect(isBlank('a')).toBe(false);
+ });
+});
+
+describe('assert', () => {
+ it('조건이 참이면 에러를 던지지 않는다', () => {
+ expect(() => assert(true)).not.toThrowError();
+ });
+
+ it('조건이 거짓이면 에러를 던진다', () => {
+ expect(() => assert(false)).toThrowError('Invalid condition');
+ });
+
+ it('조건이 거짓이고 에러 메시지가 제공된 경우 사용자 정의 에러 메시지를 던져야 한다', () => {
+ const customMessage = 'Custom error message';
+
+ expect(() => assert(false, customMessage)).toThrowError(customMessage);
+ });
+});
diff --git a/src/acronymizeHangul.spec.ts b/src/acronymizeHangul.spec.ts
new file mode 100644
index 00000000..a4d8b91e
--- /dev/null
+++ b/src/acronymizeHangul.spec.ts
@@ -0,0 +1,18 @@
+import { acronymizeHangul } from './acronymizeHangul';
+
+describe('acronymizeHangul', () => {
+ it('한글 문장 단어중 첫 문자만 뽑은 리스트를 반환', () => {
+ expect(acronymizeHangul('치킨과 맥주')).toHaveLength(2);
+ expect(acronymizeHangul('치킨과 맥주').join('')).toBe('치맥');
+
+ expect(acronymizeHangul('버스 충전 카드')).toHaveLength(3);
+ expect(acronymizeHangul('버스 충전 카드').join('')).toBe('버충카');
+ });
+ it('한글이 아닌 문장 넣었을 때', () => {
+ expect(() => acronymizeHangul('test test')).toThrowError('"test test" is not a valid hangul string');
+ });
+
+ it('한글과 영어가 섞인 문장을 넣었을 때', () => {
+ expect(() => acronymizeHangul('고기와 Cheese')).toThrowError('"고기와 Cheese" is not a valid hangul string');
+ });
+});
diff --git a/src/acronymizeHangul.ts b/src/acronymizeHangul.ts
new file mode 100644
index 00000000..9103ec40
--- /dev/null
+++ b/src/acronymizeHangul.ts
@@ -0,0 +1,12 @@
+import { parseHangul } from './_internal/hangul';
+
+/**
+ *
+ * @param getHangulAcronym
+ * @description
+ * 한글 문장을 입력받아서, 해당 한글 문장의 초성을을 리턴해줍니다.
+ * 한글 문장이 아닌, 문장은 취급하지않습니다. 추가로 한글 문장 + 영어 문장의 경우에도 취급하지않습니다.
+ */
+export function acronymizeHangul(hangul: string) {
+return parseHangul(hangul).split(' ').map(word => word.charAt(0));
+}
diff --git a/src/amountToHangul.spec.ts b/src/amountToHangul.spec.ts
index 9fcb3bbc..0ba591a6 100644
--- a/src/amountToHangul.spec.ts
+++ b/src/amountToHangul.spec.ts
@@ -1,16 +1,29 @@
import { amountToHangul } from './amountToHangul';
describe('amountToHangul', () => {
- it('숫자로 된 금액을 한글로 표기', () => {
+ it('금액 문자열을 한글로 표기', () => {
expect(amountToHangul('15,201,100')).toEqual('일천오백이십만천백');
- expect(amountToHangul('120,030원')).toEqual('일십이만삼십'); // 숫자 외 문자 무시
- expect(amountToHangul('392.24')).toEqual('삼백구십이'); // 소수점 무시
expect(amountToHangul('100000000')).toEqual('일억');
expect(amountToHangul('100000100')).toEqual('일억백');
});
it('숫자로 된 금액이 80글자를 넘을 시 에러 발생', () => {
- const longNumberString = '1'.repeat(81);
+ const longNumberString = '1'.repeat(81);
expect(() => amountToHangul(longNumberString)).toThrowError(`convert range exceeded : ${longNumberString}`);
+ })
+
+ it('숫자 외 문자를 무시하여 반환', () => {
+ expect(amountToHangul('120,030원')).toEqual('일십이만삼십');
+ });
+
+ it('소수점이 있는 경우도 표기', () => {
+ expect(amountToHangul('392.24')).toEqual('삼백구십이점이사');
+ expect(amountToHangul('12345.6789')).toEqual('일만이천삼백사십오점육칠팔구');
+ });
+
+ it('금액 숫자를 한글로 표기', () => {
+ expect(amountToHangul(15_201_100)).toEqual('일천오백이십만천백');
+ expect(amountToHangul(100000100)).toEqual('일억백');
+ expect(amountToHangul(392.24)).toEqual('삼백구십이점이사');
});
});
diff --git a/src/amountToHangul.ts b/src/amountToHangul.ts
index 8708e817..c0afbd53 100644
--- a/src/amountToHangul.ts
+++ b/src/amountToHangul.ts
@@ -1,35 +1,71 @@
-export const HANGUL_DIGITS = ['', '만', '억', '조', '경', '해', '자', '양', '구', '간', '정', '재', '극', '항하사', '아승기', '나유타', '불가사의', '무량대수', '겁', '업'];
+export const HANGUL_DIGITS = [
+ '',
+ '만',
+ '억',
+ '조',
+ '경',
+ '해',
+ '자',
+ '양',
+ '구',
+ '간',
+ '정',
+ '재',
+ '극',
+ '항하사',
+ '아승기',
+ '나유타',
+ '불가사의',
+ '무량대수',
+ '겁',
+ '업',
+];
export const HANGUL_DIGITS_MAX = HANGUL_DIGITS.length * 4;
export const HANGUL_NUMBERS = ['', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구'];
export const HANGUL_CARDINAL = ['', '십', '백', '천'];
-// https://ko.dict.naver.com/#/correct/korean/info?seq=602
-// https://github.com/crucifyer/koreanCardinalOrdinal
-export function amountToHangul(str: string) {
- str = str.replace(/\..*$/, '') // 소수점 지원 안함
- .replace(/[^\d]+/g, ''); // , 표기 등 오류내지 않음
- if(str.length > HANGUL_DIGITS_MAX) {
- throw new Error('convert range exceeded : ' + str);
+export function amountToHangul(amount: string | number) {
+ const [integerPart, decimalPart] = String(amount)
+ .replace(/[^\d.]+/g, '')
+ .split('.');
+
+ if (integerPart.length > HANGUL_DIGITS_MAX) {
+ throw new Error(`convert range exceeded : ${amount}`);
}
+
const result = [];
let pronunDigits = true;
- for(let i = 0; i < str.length - 1; i ++) {
- const d = str.length - i - 1;
- if(str[i] > '1' || d % 4 === 0 || i === 0) {
- const tnum = HANGUL_NUMBERS[parseInt(str[i])];
- if(tnum) {
- result.push(tnum);
+
+ for (let i = 0; i < integerPart.length - 1; i++) {
+ const digit = integerPart.length - i - 1;
+
+ if (integerPart[i] > '1' || digit % 4 === 0 || i === 0) {
+ const hangulNumber = HANGUL_NUMBERS[Number(integerPart[i])];
+
+ if (hangulNumber) {
+ result.push(hangulNumber);
pronunDigits = true;
}
}
- if(pronunDigits && d % 4 === 0) {
- result.push(HANGUL_DIGITS[d / 4]);
+
+ if (pronunDigits && digit % 4 === 0) {
+ result.push(HANGUL_DIGITS[digit / 4]);
pronunDigits = false;
}
- if(str[i] !== '0') {
- result.push(HANGUL_CARDINAL[d % 4]);
+
+ if (integerPart[i] !== '0') {
+ result.push(HANGUL_CARDINAL[digit % 4]);
+ }
+ }
+ result.push(HANGUL_NUMBERS[Number(integerPart[integerPart.length - 1])]);
+
+ if (decimalPart) {
+ result.push('점');
+
+ for (let i = 0; i < decimalPart.length; i++) {
+ result.push(HANGUL_NUMBERS[Number(decimalPart[i])]);
}
}
- result.push(HANGUL_NUMBERS[parseInt(str[str.length - 1])]);
+
return result.join('');
}
diff --git a/src/chosungIncludes.ts b/src/chosungIncludes.ts
index 254d1f30..3617a0c0 100644
--- a/src/chosungIncludes.ts
+++ b/src/chosungIncludes.ts
@@ -2,13 +2,13 @@ import { disassembleHangulToGroups } from './disassemble';
import { canBeChosung, getChosung } from './utils';
export function chosungIncludes(x: string, y: string) {
- const trimmedY = y.replace(/\s/g, '');
+ const trimmedY = y.replace(/\s+/g, '');
if (!isOnlyChosung(trimmedY)) {
return false;
}
- const chosungX = getChosung(x).replace(/\s/g, '');
+ const chosungX = getChosung(x).replace(/\s+/g, '');
const chosungY = trimmedY;
return chosungX.includes(chosungY);
diff --git a/src/constants.ts b/src/constants.ts
index 217defe3..6579e3cc 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -3,6 +3,16 @@ export const COMPLETE_HANGUL_END_CHARCODE = '힣'.charCodeAt(0);
export const NUMBER_OF_JONGSUNG = 28;
export const NUMBER_OF_JUNGSUNG = 21;
+const _JASO_HANGUL_NFD = [...'각힣'.normalize('NFD')].map(char => char.charCodeAt(0)); // NFC 에 정의되지 않은 문자는 포함하지 않음
+export const JASO_HANGUL_NFD = {
+ START_CHOSEONG: _JASO_HANGUL_NFD[0], // ㄱ
+ START_JUNGSEONG: _JASO_HANGUL_NFD[1], // ㅏ
+ START_JONGSEONG: _JASO_HANGUL_NFD[2], // ㄱ
+ END_CHOSEONG: _JASO_HANGUL_NFD[3], // ㅎ
+ END_JUNGSEONG: _JASO_HANGUL_NFD[4], // ㅣ
+ END_JONGSEONG: _JASO_HANGUL_NFD[5], // ㅎ
+}
+
/**
* ㄱ -> 'ㄱ'
* ㄳ -> 'ㄱㅅ' 으로 나눈다.
diff --git a/src/extractHangul.spec.ts b/src/extractHangul.spec.ts
new file mode 100644
index 00000000..b3fd84dd
--- /dev/null
+++ b/src/extractHangul.spec.ts
@@ -0,0 +1,27 @@
+import { extractHangul } from './extractHangul';
+
+describe('extractHangul', () => {
+ it('숫자와 알파벳과 특수문자를 제외한 한글 반환', () => {
+ expect(extractHangul('안녕하세요1234abc!@#')).toBe('안녕하세요');
+ });
+
+ it('한글이 없는 문자열', () => {
+ expect(extractHangul('1234abc')).toBe('');
+ });
+
+ it('한글과 공백을 제외한 다른 문자는 제거', () => {
+ expect(extractHangul('한글과 영어가 섞인 문장입니다. Hello!')).toBe('한글과 영어가 섞인 문장입니다 ');
+ });
+
+ it('escape 문자열 유지', () => {
+ expect(extractHangul('한글과\n\t줄바꿈')).toBe('한글과\n\t줄바꿈');
+ });
+
+ it('모음은 제거하지 않음', () => {
+ expect(extractHangul('ㅠㅠ')).toBe('ㅠㅠ');
+ });
+
+ it('자음은 제거하지 않음', () => {
+ expect(extractHangul('ㄱㄴㄱㄴ')).toBe('ㄱㄴㄱㄴ');
+ });
+});
diff --git a/src/extractHangul.ts b/src/extractHangul.ts
new file mode 100644
index 00000000..d023a674
--- /dev/null
+++ b/src/extractHangul.ts
@@ -0,0 +1,18 @@
+/**
+ * @name extractHangul
+ * @description
+ * 문자열을 입력받고 한글만 추출해 반환합니다.
+ *
+ * @param {string} chars 모든 문자열
+ *
+ * @example
+ * extractHangul('안녕하세요1234abc') // '안녕하세요'
+ * extractHangul('abcde') // ''
+ * extractHangul('안녕하세요ㄱㄴ') // '안녕하세요ㄱㄴ'
+ * extractHangul('안녕하세요 만나서 반갑습니다') // '안녕하세요 만나서 반갑습니다'
+ * extractHangul('가나다!-29~라마바.,,사') // '가나다라마바사'
+ */
+
+export function extractHangul(str: string): string {
+ return str.replace(/[^ㄱ-ㅎㅏ-ㅣ가-힣\s]+/g, '');
+}
diff --git a/src/index.ts b/src/index.ts
index be7f02a3..5e839a05 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,10 +1,21 @@
-export * from './assemble';
-export * from './chosungIncludes';
-export * from './combineHangulCharacter';
-export * from './convertQwertyToHangulAlphabet';
-export * from './disassemble';
-export * from './disassembleCompleteHangulCharacter';
-export * from './hangulIncludes';
-export * from './josa';
-export * from './removeLastHangulCharacter';
-export * from './utils';
+export { assembleHangul } from './assemble';
+export { chosungIncludes } from './chosungIncludes';
+export { combineHangulCharacter, combineVowels, curriedCombineHangulCharacter } from './combineHangulCharacter';
+export { convertQwertyToHangul, convertQwertyToHangulAlphabet } from './convertQwertyToHangulAlphabet';
+export { disassembleHangul, disassembleHangulToGroups } from './disassemble';
+export { disassembleCompleteHangulCharacter } from './disassembleCompleteHangulCharacter';
+export { hangulIncludes } from './hangulIncludes';
+export { josa } from './josa';
+export { removeLastHangulCharacter } from './removeLastHangulCharacter';
+export {
+ canBeChosung,
+ canBeJongsung,
+ canBeJungsung,
+ getChosung,
+ hasBatchim,
+ hasProperty,
+ hasSingleBatchim,
+ hasValueInReadOnlyStringList,
+} from './utils';
+export { extractHangul } from './extractHangul';
+export { acronymizeHangul } from './acronymizeHangul';
diff --git a/src/removeLastHangulCharacter.spec.ts b/src/removeLastHangulCharacter.spec.ts
index 17ce98f7..0445bc43 100644
--- a/src/removeLastHangulCharacter.spec.ts
+++ b/src/removeLastHangulCharacter.spec.ts
@@ -3,6 +3,7 @@ import { removeLastHangulCharacter } from './removeLastHangulCharacter';
describe('removeLastHangulCharacter', () => {
it('마지막 문자가 겹받침인 경우 홑받침으로 바꾼다.', () => {
expect(removeLastHangulCharacter('안녕하세요 값')).toBe('안녕하세요 갑');
+ expect(removeLastHangulCharacter('안녕하세요 값이')).toBe('안녕하세요 값ㅇ');
});
it('마지막 문자가 초성과 중성의 조합으로 끝날 경우 초성만 남긴다.', () => {
expect(removeLastHangulCharacter('프론트엔드')).toBe('프론트엔ㄷ');
diff --git a/src/removeLastHangulCharacter.ts b/src/removeLastHangulCharacter.ts
index 20c7ac15..59838522 100644
--- a/src/removeLastHangulCharacter.ts
+++ b/src/removeLastHangulCharacter.ts
@@ -20,25 +20,13 @@ import { excludeLastElement } from './_internal';
* removeLastHangulCharacter('신세계') // 신세ㄱ
*/
export function removeLastHangulCharacter(words: string) {
- const disassembledGroups = disassembleHangulToGroups(words);
- const lastCharacter = disassembledGroups[disassembledGroups.length - 1];
-
+ const lastCharacter = words[words.length - 1];
if (lastCharacter == null) {
return '';
}
-
- const withoutLastCharacter = disassembledGroups
- .filter(v => v !== lastCharacter)
- .map(([first, middle, last]) => {
- if (middle != null) {
- return combineHangulCharacter(first, middle, last);
- }
-
- return first;
- });
-
- const [[first, middle, last]] = excludeLastElement(lastCharacter);
+ const disassembleLastCharacter = disassembleHangulToGroups(lastCharacter);
+ const [[first, middle, last]] = excludeLastElement(disassembleLastCharacter[0]);
const result = middle != null ? combineHangulCharacter(first, middle, last) : first;
- return [...withoutLastCharacter, result].join('');
+ return [words.substring(0, words.length - 1), result].join('');
}
diff --git a/src/utils.spec.ts b/src/utils.spec.ts
index cacb0ce0..58dc04e4 100644
--- a/src/utils.spec.ts
+++ b/src/utils.spec.ts
@@ -34,6 +34,18 @@ describe('hasBatchim', () => {
expect(hasBatchim('')).toBe(false);
});
});
+
+ describe('완성된 한글이 아닌 경우', () => {
+ it('한글이 자음 또는 모음으로만 구성된 경우 false를 반환한다.', () => {
+ expect(hasBatchim('ㄱ')).toBe(false);
+ expect(hasBatchim('ㅏ')).toBe(false);
+ });
+
+ it('한글 외의 문자를 입력하면 false를 반환한다', () => {
+ expect(hasBatchim('cat')).toBe(false);
+ expect(hasBatchim('!')).toBe(false);
+ });
+ });
});
describe('hasSingleBatchim', () => {
@@ -54,6 +66,12 @@ describe('hasSingleBatchim', () => {
expect(hasSingleBatchim('토')).toBe(false);
expect(hasSingleBatchim('서')).toBe(false);
});
+
+ it('한글 외의 문자를 입력하면 false를 반환한다.', () => {
+ expect(hasSingleBatchim('cat')).toBe(false);
+ expect(hasSingleBatchim('')).toBe(false);
+ expect(hasSingleBatchim('?')).toBe(false);
+ });
});
});
diff --git a/src/utils.ts b/src/utils.ts
index 5213d9c8..eb336371 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,10 +1,22 @@
import {
+ COMPLETE_HANGUL_START_CHARCODE,
+ COMPLETE_HANGUL_END_CHARCODE,
HANGUL_CHARACTERS_BY_FIRST_INDEX,
HANGUL_CHARACTERS_BY_LAST_INDEX,
HANGUL_CHARACTERS_BY_MIDDLE_INDEX,
+ NUMBER_OF_JONGSUNG,
+ JASO_HANGUL_NFD,
} from './constants';
-import { disassembleHangul, disassembleHangulToGroups } from './disassemble';
-import { disassembleCompleteHangulCharacter } from './disassembleCompleteHangulCharacter';
+import { disassembleHangulToGroups } from './disassemble';
+
+const EXTRACT_CHOSEONG_REGEX = new RegExp(
+ `[^\\u${JASO_HANGUL_NFD.START_CHOSEONG.toString(16)}-\\u${JASO_HANGUL_NFD.END_CHOSEONG.toString(16)}ㄱ-ㅎ\\s]+`,
+ 'ug'
+);
+const CHOOSE_NFD_CHOSEONG_REGEX = new RegExp(
+ `[\\u${JASO_HANGUL_NFD.START_CHOSEONG.toString(16)}-\\u${JASO_HANGUL_NFD.END_CHOSEONG.toString(16)}]`,
+ 'g'
+);
/**
* @name hasBatchim
@@ -26,9 +38,14 @@ export function hasBatchim(str: string) {
if (lastChar == null) {
return false;
}
+ const charCode = lastChar.charCodeAt(0);
+ const isCompleteHangul = COMPLETE_HANGUL_START_CHARCODE <= charCode && charCode <= COMPLETE_HANGUL_END_CHARCODE;
- const disassembled = disassembleCompleteHangulCharacter(lastChar);
- return disassembled != null && disassembled.last !== '';
+ if (!isCompleteHangul) {
+ return false;
+ }
+
+ return (charCode - COMPLETE_HANGUL_START_CHARCODE) % NUMBER_OF_JONGSUNG > 0;
}
/**
@@ -49,12 +66,18 @@ export function hasBatchim(str: string) {
export function hasSingleBatchim(str: string) {
const lastChar = str[str.length - 1];
- if (lastChar == null || hasBatchim(lastChar) === false) {
+ if (lastChar == null) {
return false;
}
+ const charCode = lastChar.charCodeAt(0);
+ const isCompleteHangul = COMPLETE_HANGUL_START_CHARCODE <= charCode && charCode <= COMPLETE_HANGUL_END_CHARCODE;
- const disassembled = disassembleHangul(lastChar);
- return disassembled.length === 3;
+ if (!isCompleteHangul) {
+ return false;
+ }
+
+ const batchimCode = (charCode - COMPLETE_HANGUL_START_CHARCODE) % NUMBER_OF_JONGSUNG;
+ return HANGUL_CHARACTERS_BY_LAST_INDEX[batchimCode].length === 1;
}
/**
@@ -73,9 +96,9 @@ export function hasSingleBatchim(str: string) {
* getChosung('띄어 쓰기') // 'ㄸㅇ ㅆㄱ'
*/
export function getChosung(word: string) {
- return disassembleHangulToGroups(word).reduce((chosung, [consonant]) => {
- return `${chosung}${consonant}`;
- }, '');
+ return word.normalize('NFD')
+ .replace(EXTRACT_CHOSEONG_REGEX, '') // NFD ㄱ-ㅎ, NFC ㄱ-ㅎ 외 문자 삭제
+ .replace(CHOOSE_NFD_CHOSEONG_REGEX, $0 => HANGUL_CHARACTERS_BY_FIRST_INDEX[$0.charCodeAt(0) - 0x1100]); // NFD to NFC
}
/**
diff --git a/tsconfig.json b/tsconfig.json
index 08a6927f..2fbc5a46 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,5 +8,5 @@
"strict": true,
"skipLibCheck": true
},
- "include": ["src", ".eslintrc.js"]
+ "include": ["src", ".eslintrc.js", "packlint.config.mjs"]
}