toss / es-hangul Goto Github PK
View Code? Open in Web Editor NEWA modern JavaScript library for handling Hangul characters.
Home Page: https://es-hangul.slash.page/
License: MIT License
A modern JavaScript library for handling Hangul characters.
Home Page: https://es-hangul.slash.page/
License: MIT License
현재 convertQwertyToHangulAlphabet
와 disassembleCompleteHangulCharacter
함수를 export하고 있지 않은데, 그 이유가 있는지 궁금합니다.
// src/index.ts
export * from './chosungIncludes';
export * from './disassemble';
export * from './hangulIncludes';
export * from './josa';
export * from './utils';
export * from './assemble';
export * from './combineHangulCharacter';
export * from './removeLastHangulCharacter';
+ export * from './convertQwertyToHangulAlphabet';
+ export * from './disassembleCompleteHangulCharacter';
No response
In official docs, disassembleHangul
works as "한글 문자열을 글자별로 초성/중성/종성 단위로 완전히 분리하여" (in English, "seperates Korean works into onset/nucleus/coda syllables"), which is not what the function actually does.
h.disassembleHangul('개') // I think this would be 'ㄱㅐ', and it is.
h.disassembleHangul('과') // I think this would be 'ㄱㅘ', but it isn't. it's `ㄱㅗㅏ`
skipped, because i'm unsure whether this is intentional or not.
Here's full test cases below. I think every assertion should be passed.
// double consonant 1 (ㄲ, ㄸ, ㅃ, ㅆ, ㅉ)
// onset
h.disassembleHangul('까') == 'ㄲㅏ'
h.disassembleHangul('따') == 'ㄸㅏ'
h.disassembleHangul('빠') == 'ㅃㅏ'
h.disassembleHangul('싸') == 'ㅆㅏ'
h.disassembleHangul('짜') == 'ㅉㅏ'
// code
h.disassembleHangul('갂') == 'ㄱㅏㄲ'
h.disassembleHangul('갔') == 'ㄱㅏㅆ'
// double consonant 2 (ㄳ, ㄵ, ㄶ, ㄺ, ㄻ, ㄼ, ㄽ, ㄾ, ㄿ, ㅀ, ㅄ)
// code
h.disassembleHangul('갃') == 'ㄱㅏㄳ' // false
h.disassembleHangul('갅') == 'ㄱㅏㄵ' // false
h.disassembleHangul('갆') == 'ㄱㅏㄶ' // false
h.disassembleHangul('갉') == 'ㄱㅏㄺ' // false
h.disassembleHangul('갊') == 'ㄱㅏㄻ' // false
h.disassembleHangul('갋') == 'ㄱㅏㄼ' // false
h.disassembleHangul('갌') == 'ㄱㅏㄽ' // false
h.disassembleHangul('갍') == 'ㄱㅏㄾ' // false
h.disassembleHangul('갎') == 'ㄱㅏㄿ' // false
h.disassembleHangul('갏') == 'ㄱㅏㅀ' // false
h.disassembleHangul('값') == 'ㄱㅏㅄ' // false
// single vowel (ㅏ, ㅑ, ㅓ, ㅕ, ㅗ, ㅛ, ㅜ, ㅠ, ㅡ, ㅣ)
// nucleus
h.disassembleHangul('가') == 'ㄱㅏ'
h.disassembleHangul('갸') == 'ㄱㅑ'
h.disassembleHangul('거') == 'ㄱㅓ'
h.disassembleHangul('겨') == 'ㄱㅕ'
h.disassembleHangul('고') == 'ㄱㅗ'
h.disassembleHangul('교') == 'ㄱㅛ'
h.disassembleHangul('구') == 'ㄱㅜ'
h.disassembleHangul('규') == 'ㄱㅠ'
h.disassembleHangul('그') == 'ㄱㅡ'
h.disassembleHangul('기') == 'ㄱㅣ'
// double vowel (ㅐ, ㅒ, ㅔ, ㅖ, ㅘ, ㅙ, ㅚ, ㅝ, ㅞ, ㅟ, ㅢ)
// nucleus
h.disassembleHangul('개') == 'ㄱㅐ'
h.disassembleHangul('걔') == 'ㄱㅒ'
h.disassembleHangul('게') == 'ㄱㅔ'
h.disassembleHangul('계') == 'ㄱㅖ'
h.disassembleHangul('과') == 'ㄱㅘ' // false
h.disassembleHangul('괘') == 'ㄱㅙ' // false
h.disassembleHangul('괴') == 'ㄱㅚ' // false
h.disassembleHangul('궈') == 'ㄱㅝ' // false
h.disassembleHangul('궤') == 'ㄱㅞ' // false
h.disassembleHangul('귀') == 'ㄱㅟ' // false
h.disassembleHangul('긔') == 'ㄱㅢ' // false
인자로 문자를 받아 초성, 중성, 종성 위치가 가능한 문자인지 검사하는 함수입니다.
함수의 동작은 소스 내부의 HANGUL_CHARACTERS_BY_FIRST_INDEX
, HANGUL_CHARACTERS_BY_MIDDLE_INDEX
, HANGUL_CHARACTERS_BY_LAST_INDEX
에 종속됩니다.
이 함수들을 추후 #18 이슈의 구현 시 검사 로직을 추상화할 때 사용하면 좋을 것 같습니다.
canBeChosung('ㄱ') // true
canBeChosung('ㅃ') // true
canBeChosung('ㄱㅅ') // false
canBeChosung('ㅏ') // false
canBeJungsung('ㅏ') // true
canBeJungsung('ㅗㅏ') // true
canBeJungsung('ㅘ') // false
canBeJungsung('ㄱ') // false
canBeJongsung('ㄱ') // true
canBeJongsung('ㄱㅅ') // true
canBeJongsung('ㅘ') // false
canBeJongsung('ㄹㅍ') // false
No response
함수의 동작이 HANGUL_CHARACTERS_BY_FIRST_INDEX
, HANGUL_CHARACTERS_BY_MIDDLE_INDEX
, HANGUL_CHARACTERS_BY_LAST_INDEX
의 구성에 종속된다면, ㅘ
, ㄳ
값은 false
로 계산됩니다.
ㄳ
같은 값은 실제로 유저가 입력하기에는 번거로운 값이기 때문에 ㄱㅅ
와 같은 형태로 검사하는 것이 합리적이지만,
ㅘ
와 같이 조합된 모음은 유저가 쉽게 입력할 수 있는 값이기 때문에 어떻게 처리해줘야 할지에 대한 고민이 필요합니다.
josa.pick()
함수를 es-hangul
에서 사용할 수 있지만, 문서화가 되어 있지 않습니다.
https://github.com/toss/es-hangul/blob/main/src/josa.ts#L29
No response
No response
It seems that the correct romanization for 한글 is Hangeul. Is there any particular reason to use Hangul?
assemble(['ㄱ', 'ㅗ', 'ㅎ', 'ㅑ', 'ㅇ']); // 고향
assemble(['ㄱ', 'ㅗ', 'ㅎ']); // �곻
assemble(['프론트엔', 'ㄷ']); // 프론트엔ㄷ
disassemble의 역함수를 구현하면 될 것 같습니다.
이 함수는 한글 자음과 모음을 조합하여 완성된 한글 단어를 만들거나, 한글과 다른 문자를 조합할 때 사용될 것 같습니다.
영문 자판으로 입력하면 그에 상응하는 한글 자모 string을 반환해주는 기능을 가진 함수.
영문 자판과 한글 자판을 맵핑하는 상수 객체를 선언하여 구현하면 될 거 같습니다.
// constants.ts
/*
* 영문 대소문자를 구분한 자판 맵 객체
*/
export const KEBOARD_MAP = {
q: 'ㅂ',
Q: 'ㅃ',
w: 'ㅈ',
//...한/영 자판 맵핑
} as const;
// convertEnglishToHangul.ts
import { KEBOARD_MAP } from './constants';
import { disassembleHangul } from './disassemble';
/**
* 영문 자판인 경우 한글 자판으로 치환합니다. 그렇지 않은 경우 입력 문자를 반환합니다.
* @param 한글로 변환하고자 하는 영문 자판.
* @returns 한글 자모로 변환된 문자열
*/
export function convertEngKeboardToHangul(engKeyboardText: string): string {
return engKeyboardText.split('').map((inputText) => keyboardMap[inputText] ?? disassembleHangul(inputText)).join;
}
#18 문자 배열을 입력 받아 한글로 함성하는 함수와 함께 이용하면 영타로 입력된 값을 온전한 한글로 치환할 수 있을 것으로 기대합니다.
combineHangulCharacter(...convertEngKeboardToHangul('vm').split('')) // Output '프'
combineHangulCharacter(...convertEngKeboardToHangul('fhs').split('')) // Output '론'
안녕하세요 :) 흥미롭게 보다가 받침 유무에 따라 달라지는 이형태 호격조사인 여/이여
에 대한 처리가 없길래 이슈 남깁니다. 혹시 여/이여
를 포함하지 않은 이유가 있을까요?
ex
그대'여' / 그대들'이여'
친구'여' / 사랑'이여'
cf. 여
No response
No response
우선, 한글을 사용한 멋진 오픈소스 프로젝트에 응원의 글을 남깁니다. 👍
현재 코드에서 아래의 사항을 개선하면 더 멋진 코드가 될 것 같아 issue를 남깁니다. :)
- 테스트 로직과 값 분리.
- each 메서드를 이용한 코드 반복 제거.
- 테스트 관심사별 describe 세분화.
하단에 리팩터링 예시코드를 남겨두었으니 편하게 피드백주시면 감사하겠습니다.
import { chosungIncludes } from './chosungIncludes';
describe('chosungIncludes', () => {
it('should return true when "ㅍㄹㅌ" is entered for searching "프론트엔드"', () => {
expect(chosungIncludes('프론트엔드', 'ㅍㄹㅌ')).toBe(true);
});
it('should return true when "ㅍㄹㅌ" is entered for searching "00프론트엔드"', () => {
expect(chosungIncludes('00프론트엔드', 'ㅍㄹㅌ')).toBe(true);
});
it('should return false when "ㅍㅌ" is entered for searching "프론트엔드"', () => {
expect(chosungIncludes('프론트엔드', 'ㅍㅌ')).toBe(false);
});
it('should return true when "ㅍㄹㅌㅇㄷㄱㅂㅈ" is entered for searching "프론트엔드 개발자"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷㄱㅂㅈ')).toBe(true);
});
it('should return true when "ㅍㄹㅌㅇㄷ ㄱㅂㅈ" is entered for searching "프론트엔드 개발자"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ')).toBe(true);
});
// ...codes
});
기대 효과:
import { chosungIncludes } from './chosungIncludes';
describe('chosungIncludes', () => {
const allTestCases = [
{ search: 'ㅍㄹㅌ', target: '프론트엔드', expected: true },
{ search: 'ㅍㄹㅌ', target: '00프론트엔드', expected: true },
{ search: 'ㅍㄹㅌㅇㄷㄱㅂㅈ', target: '프론트엔드 개발자', expected: true },
{ search: 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ', target: '프론트엔드 개발자', expected: true },
{ search: 'ㅍㅌ', target: '프론트엔드', expected: false },
{ search: '푸롴트', target: '프론트엔드', expected: false },
{ search: 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ', target: ' ', expected: false },
];
it.each(allTestCases)(
'should return $expected when $search is entered for searching $target',
({ search, target, expected }) => {
expect(chosungIncludes(target, search)).toBe(expected);
}
);
});
현재 리팩터링 된 코드는, 하나의 describe 내에서 초성이 포함되어있다고 판단되는 경우
와 초성이 포함되어있다고 판단되지 않는 경우
를 전부 테스트한다는 아쉬움이 있습니다. 이는, 관심사에 따라 describe를 세분화하여 해결할 수 있습니다.
import { chosungIncludes } from './chosungIncludes';
describe('chosungIncludes', () => {
describe('초성이 포함되어있다고 판단되는 경우', () => {
const testCases = [
{ search: 'ㅍㄹㅌ', target: '프론트엔드' },
{ search: 'ㅍㄹㅌ', target: '00프론트엔드' },
{ search: 'ㅍㄹㅌㅇㄷㄱㅂㅈ', target: '프론트엔드 개발자' },
{ search: 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ', target: '프론트엔드 개발자' },
];
it.each(testCases)('should return true when $search is entered for searching $target', ({ search, target }) => {
const result = chosungIncludes(target, search);
expect(result).toBe(true);
});
});
describe('초성이 포함되어있다고 판단되지 않는 경우', () => {
const testCases = [
{ search: 'ㅍㅌ', target: '프론트엔드' },
{ search: '푸롴트', target: '프론트엔드' },
];
it.each(testCases)('should return false when $search is entered for searching $target', ({ search, target }) => {
const result = chosungIncludes(target, search);
expect(result).toBe(false);
});
});
});
No response
한국어 문장 / 단어를 주입하면, 로마자로 변환한 string을 반환해주는 기능을 가진 함수
/**
* 한글 텍스트를 로마자로 변환합니다.
* @param hangulText 변환하고자 하는 한글 텍스트.
* @returns 로마자로 변환된 텍스트.
*/
function convertToRoman(hangulText: string): string {
return "로마자_텍스트";
}
test cases
test('should convert simple Hangul to Roman', () => {
expect(convertToRoman('안녕하세요')).toEqual('annyeonghaseyo');
});
test('should handle empty input', () => {
expect(convertToRoman('')).toEqual('');
});
test('should process Hangul with special characters', () => {
expect(convertToRoman('한국어!')).toEqual('hangugeo!');
});
과일(딸기)이 좋다.
위처럼 괄호 뒤 조사의 경우 괄호 앞에 단어를 기준으로 조사를 붙이는게 관행이나, 이를 제대로 인식하지 못합니다.
참고: https://www.korean.go.kr/front/onlineQna/onlineQnaView.do?mn_id=216&qna_seq=252613&pageIndex=1
No response
import { josa } from "es-hangul";
const word1 = "비고(총수량)";
const word2 = "총수량(비고)";
const word3 = "()";
console.log(josa(word1, "이/가") + " 비어 있습니다.");
console.log(josa(word2, "이/가") + " 비어 있습니다.");
console.log(josa(word3, "이/가") + " 비어 있습니다.");
const word4 = "대지의 여신(이하 가이아)";
const word5 = "가이아(이하 대지의 여신)";
const word6 = "()";
console.log(josa(word4, "을/를") + " 숭배하라");
console.log(josa(word5, "을/를") + " 숭배하라");
console.log(josa(word6, "을/를") + " 숭배하라");
# 출력값
비고(총수량)가 비어 있습니다.
총수량(비고)가 비어 있습니다.
()가 비어 있습니다.
대지의 여신(이하 가이아)를 숭배하라
가이아(이하 대지의 여신)를 숭배하라
()를 숭배하라
')' 을 기준으로 조사를 처리하여 발생하는 것으로 보여 괄호 앞 단어를 찾아 그에 맞는 조사를 붙여줘야 할것으로 보입니다.
No response
https://es-hangul.slash.page/docs/introduction
unknown제거를 위해 codecov설정 필요합니다
codecov는 open source에 무료로 제공되는 것으로 알고 있습니다.
이 레포지토리에 GitHub bot 설치가 필요합니다.
https://github.com/apps/codecov
codecov.yml이 추가와 coverage관련 파일의 업로드를 위해 ci에 추가되어야합니다.
codecov.yml로 설정 PR은 올려놓겠습니다. Circle CI를 사용중으로 보이는데 업로드 방식은 토스에서 추가해주시면 좋겠어요
https://about.codecov.io/tool/circleci/
숫자를 받았을 때 n억 n천 n원
과 같은 원화 표기 문자열로 변환해주는 기능을 직접 구현하다가 라이브러리에 기여하면 좋겠다는 생각이 들어 제안드려봅니다.
참고로 현행 한글 맞춤법 체계에서는 만 단위로 끊어 쓰도록 하고 있고, 이를 한글과 숫자 양쪽으로 표현해주도록 하면 좋을 것 같습니다.
No response
다음과 같은 테스트 케이스를 추가했으나 false
가 반환되어 테스트가 실패하는 현상을 발견했습니다.
it('should return true when "ㅍㄹㅌㅇㄷ ㄱㅂㅈ" is entered for searching "프론트엔드"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ')).toBe(true);
});
'ㅍㄹㅌㅇㄷ ㄱㅂㅈ' 같은 입력에서도 공백이 포함되어 있어도 정상적으로 동작해야 합니다. 특히 다음 코드 부분에서 공백을 제거하는 로직이 포함되어 있어, 의도하지 않은 버그로 보입니다.
const initialConsonantsY = getFirstConsonants(y).replace(/\s/g, '');
예를 들어 '빌즈 강남'을 'ㅂㅈ ㄱㄴ'으로 검색했을 때 일치하는 결과가 반환되면 사용자 경험에 좋을 것입니다.
chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ')
판단 기준하에 다음과 같은 두 가지 방법으로 문제를 해결 혹은 개선할 수 있을 것으로 예상됩니다.
import { HANGUL_CHARACTERS_BY_FIRST_INDEX } from './constants';
import { disassembleHangulToGroups } from './disassemble';
import { getFirstConsonants, hasValueInReadOnlyStringList } from './utils';
export function chosungIncludes(x: string, y: string) {
if (!isOnlyInitialConsonant(y)) {
return false;
}
const initialConsonantsX = getFirstConsonants(x).replace(/\s/g, '');
const initialConsonantsY = getFirstConsonants(y).replace(/\s/g, '');
return initialConsonantsX.includes(initialConsonantsY);
}
/*
* @description 공백을 제외한 문자열이 한글초성으로만 주어진 경우
*/
function isOnlyInitialConsonant(str: string) {
const trimStr = str.replace(/\s/g, '');
return disassembleHangulToGroups(trimStr).every(disassembled => {
return disassembled.length === 1 && hasValueInReadOnlyStringList(HANGUL_CHARACTERS_BY_FIRST_INDEX, disassembled[0]);
});
}
it('should return true when "ㅍㄹㅌㅇㄷㄱㅂㅈ" is entered for searching "프론트엔드 개발자"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷㄱㅂㅈ')).toBe(true);
});
it('should return true when "ㅍㄹㅌㅇㄷ ㄱㅂㅈ" is entered for searching "프론트엔드 개발자"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ')).toBe(true);
});
initialConsonantsY
에서 공백을 제거할 필요 없이 처리하도록 개선하고 테스트 케이스를 추가합니다.const initialConsonantsY = getFirstConsonants(y)
it('should return true when "ㅍㄹㅌㅇㄷ ㄱㅂㅈ" is entered for searching "프론트엔드"', () => {
expect(chosungIncludes('프론트엔드 개발자', 'ㅍㄹㅌㅇㄷ ㄱㅂㅈ')).toBe(false);
});
- 두 번째 인자에 공백이 들어가더라도 기능이 정상적으로 동작해야 한다.
로 선택한다면 공백 포함 여부를 어떻게 처리할지에 대한 결정이 중요한 요소로 보입니다.
감사합니다 😁
chosungIncludes 같이 boolean 으로 딱 떨어지는 함수말고도, 정확도 계산을 위한 함수도 존재했으면 좋겠습니다.
/**
* 한글 문자열 정확도 계산
* @param targetText
* @param inputText
* @returns 정확도 혹은 철자 오타 갯수?!
*/
function calculateHangulAccuracy(targetText: string,inputText: string): number{
return 0.4;
}
자동완성 기능을 만들때, 어느정도의 오차를 허용해주는 제한을 제공하여 더 유연하게 구현가능.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.