├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SmartFormat.NET-Korean.Tests ├── Extensions │ └── KoreanFormatterTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SmartFormat.NET-Korean.Tests.csproj ├── Utilities │ └── HangulTests.cs └── packages.config ├── SmartFormat.NET-Korean.sln ├── SmartFormat.NET-Korean ├── Extensions │ └── KoreanFormatter.cs ├── Properties │ └── AssemblyInfo.cs ├── SmartFormat.NET-Korean.csproj ├── SmartFormat.NET-Korean.nuspec ├── Utilities │ └── Hangul.cs ├── nuget.exe └── packages.config └── demo_image.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/visualstudio 3 | 4 | ### VisualStudio ### 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | *.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # Since there are multiple workflows, uncomment next line to ignore bower_components 201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 202 | #bower_components/ 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | paket-files/ 250 | 251 | # FAKE - F# Make 252 | .fake/ 253 | 254 | # JetBrains Rider 255 | .idea/ 256 | *.sln.iml 257 | *.swp 258 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: SmartFormat.NET-Korean.sln 3 | install: 4 | - sudo apt-get install -y gtk-sharp2 5 | - nuget restore SmartFormat.NET-Korean.sln 6 | - nuget install NUnit.Runners -Version 2.6.4 -OutputDirectory testrunner 7 | script: 8 | - xbuild /p:Configuration=Release SmartFormat.NET-Korean.sln /p:PostBuildEvent="" 9 | - mono ./testrunner/NUnit.Runners.2.6.4/tools/nunit-console.exe ./SmartFormat.NET-Korean.Tests/bin/Release/SmartFormatKorean.Tests.dll 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, What! Studio 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of SmartFormat.NET-Korean nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KoreanFormatter 2 | 3 | [![travis][travis-img]][travis-link] 4 | [![GitHub license][license-img]][license-link] 5 | [![nuget][nuget-img]][nuget-link] 6 | 7 | C#용 [SmartFormat.NET][smartformat.net]에서 사용할 수 있는 한국어 조사 포매터입니다. python용 구현체는 [smartformat-korean][smartformat-korean]을 사용해주세요. 8 | 9 | [smartformat.net]: https://github.com/scottrippey/SmartFormat.NET 10 | [smartformat-korean]: https://github.com/what-studio/smartformat-korean 11 | [travis-img]: https://api.travis-ci.org/what-studio/SmartFormat.NET-Korean.svg?branch=master 12 | [travis-link]: https://travis-ci.org/what-studio/SmartFormat.NET-Korean 13 | [license-img]: https://img.shields.io/badge/license-New%20BSD-blue.svg 14 | [license-link]: https://raw.githubusercontent.com/what-studio/SmartFormat.NET-Korean/master/LICENSE 15 | [nuget-img]: https://img.shields.io/nuget/v/SmartFormat.NET-Korean.svg?label=nuget:%20SmartFormat.Net-Korean 16 | 17 | ## 설치 18 | 19 | Nuget 관리자 콘솔을 이용해서 설치: 20 | 21 | ```cmd 22 | PM> Install-Package SmartFormat.NET-Korean 23 | ``` 24 | 25 | 혹은 [Nuget 페이지][nuget-link]에서 다운로드 26 | 27 | [nuget-link]: https://www.nuget.org/packages/SmartFormat.NET-Korean 28 | 29 | ## 사용법 30 | 31 | ```c# 32 | namespace SmartFormatKoreanDemo 33 | { 34 | class Program 35 | { 36 | static void Main(string[] args) 37 | { 38 | Smart.Default.AddExtensions(new KoreanFormatter(Smart.Default)); 39 | 40 | Console.WriteLine(Smart.Format("{0:은} {1:을} 먹었다.", "나오", "부엉이")); 41 | Console.WriteLine(Smart.Format("{0:이} 갖고 싶어요.", "나오 피규어")); 42 | Console.WriteLine(Smart.Format("{0:이} {1:을} 춘다.", "티라노", "콩댄스")); 43 | Console.WriteLine(Smart.Format("{0:의} {1:이에요}", "넥슨 왓! 스튜디오", "듀랑고")); 44 | Console.ReadKey(); 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ![demo-image](demo_image.png) 51 | 52 | ## 자연스러운 조사 선택 53 | 54 | `의`, `도`, `만`, `보다`, `부터`, `까지`, `마저`, `조차`, `에~`, 55 | `께~`, `하~`에는 어떤 단어가 앞서도 형태가 변하지 않습니다: 56 | 57 | > 나오**의**, 모리안**의**, 키홀**의**, 나오**도**, 모리안**도**, 키홀**도** 58 | 59 | 반면 `은(는)`, `이(가)`, `을(를)`, `과(와)`는 앞선 단어의 마지막 음절의 받침 60 | 유무에 따라 형태가 달라집니다: 61 | 62 | > 나오**는**, 모리안**은**, 키홀**은** 63 | 64 | `(으)로~`도 비슷한 규칙을 따르지만 앞선 받침이 `ㄹ`일 경우엔 받침이 없는 것과 65 | 같게 취급합니다: 66 | 67 | > 나오**로**, 모리안**으로**, 키홀**로** 68 | 69 | 서술격 조사 `(이)다`는 어미가 활용되어 다양한 형태로 변형될 수 있습니다: 70 | 71 | > 나오**지만**, 모리안**이지만**, 키홀**이에요**, 나오**예요** 72 | 73 | SmartFormat 한국어 확장은 자동으로 가장 자연스러운 조사 형태를 선택합니다. 74 | 만약 어떤 형태가 자연스러운지 알 수 없을 때에는 `은(는)`, `(으)로`처럼 75 | 모든 형태를 병기합니다. 76 | 77 | ```c# 78 | // "대한민국은 민주공화국이다." 79 | Smart.Format("{0:는} {1:다}.", "대한민국", "민주공화국"); 80 | 81 | // "나오는 검은사신으로 불린다. 82 | Smart.Format("{0:은} {1:로} 불린다.", "나오", "검은사신"); 83 | ``` 84 | 85 | 단어가 숫자로 끝나더라도 자연스러운 조사 형태가 선택됩니다: 86 | 87 | ```c# 88 | // "레벨 10이" 89 | Smart.Format("레벨 {0:이}", 10); 90 | // "레벨 999가" 91 | Smart.Format("레벨 {0:이}", 999); 92 | ``` 93 | 94 | 괄호 속 단어나 구두점은 조사 형태를 선택할 때 참고하지 않습니다: 95 | 96 | ```c# 97 | // "모리안,,,이?" 98 | Smart.Format("{0:가}?", "모리안,,,"); 99 | // "<듀랑고>를 샀다." 100 | Smart.Format("{0:을} 샀다.", "<듀랑고>"); 101 | ``` 102 | 103 | 104 | ## 조사 분리 105 | 106 | 문자열에 꾸밈 태그 등을 사용하는 경우에는 조사만 분리해서 표기할 수 있습니다: 107 | 108 | ```c# 109 | // "돌날로" 110 | Smart.Format("{0}{0:-으로}", "돌날"); 111 | // "으로" 112 | Smart.Format("{0:-으로}", "마법"); 113 | ``` 114 | 115 | 116 | ## 만든이와 사용권 117 | 118 | [넥슨][nexon] [왓 스튜디오][what-studio]의 [김찬웅][kexplo]과 119 | [이흥섭][sublee]이 만들었고 [제3조항을 포함하는 BSD 허가서][bsd-3-clause]를 120 | 채택했습니다. 121 | 122 | [nexon]: http://nexon.com/ 123 | [what-studio]: https://github.com/what-studio 124 | [sublee]: http://subl.ee/ 125 | [kexplo]: http://chanwoong.kim/ 126 | [bsd-3-clause]: http://opensource.org/licenses/BSD-3-Clause 127 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.Tests/Extensions/KoreanFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using SmartFormat; 5 | using SmartFormat.Extensions; 6 | 7 | namespace KoreanParticleFormatter.Tests.Extensions 8 | { 9 | [TestFixture] 10 | class KoreanFormatterTests 11 | { 12 | [SetUp] 13 | public void Setup() 14 | { 15 | if (!Smart.Default.FormatterExtensions.Any(x => x is SmartFormat.Extensions.KoreanFormatter)) 16 | { 17 | Smart.Default.FormatterExtensions.Insert(0, new SmartFormat.Extensions.KoreanFormatter(Smart.Default)); 18 | } 19 | } 20 | 21 | [TestCase("{0:ko:아} 안녕", "나오", "나오야 안녕")] 22 | [TestCase("{0:ko:을} 칼로 깎는다", "사과", "사과를 칼로 깎는다")] 23 | [TestCase("{0:ko:으로}", "모리안", "모리안으로")] 24 | [TestCase("{0:ko:으로}", "퍼거스=대장장이", "퍼거스=대장장이로")] 25 | public void Test_simple(string format, object arg0, string expectedResult) 26 | { 27 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 28 | } 29 | 30 | [TestCase("{0:으로}", "퍼거스=대장장이", "퍼거스=대장장이로")] 31 | [TestCase("{0:으로}", "퍼거스(Ferghus)", "퍼거스(Ferghus)로")] 32 | public void Test_Implicit(string format, object arg0, string expectedResult) 33 | { 34 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 35 | } 36 | 37 | [TestCase("{0:를}", "", "을(를)")] 38 | [TestCase("{0:을}", "ㅋㅋㅋ", "ㅋㅋㅋ을(를)")] 39 | [TestCase("{0:은}", "", "은(는)")] 40 | [TestCase("{0:는}", "", "은(는)")] 41 | [TestCase("{0:이}", "", "이(가)")] 42 | [TestCase("{0:가}", "", "이(가)")] 43 | [TestCase("{0:과}", "", "과(와)")] 44 | [TestCase("{0:와}", "", "과(와)")] 45 | [TestCase("{0:으로}", "", "(으)로")] 46 | [TestCase("{0:로}", "", "(으)로")] 47 | public void Test_DoublePostposition(string format, object arg0, string expectedResult) 48 | { 49 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 50 | } 51 | 52 | [TestCase("{0:ko:으로}", "모리안(여신)", "모리안(여신)으로")] 53 | [TestCase("{0:ko:으로}", "퍼거스(대장장이(Ferghus))", "퍼거스(대장장이(Ferghus))로")] 54 | [TestCase("{0:가}?", "모리안,,,", "모리안,,,이?")] 55 | [TestCase("{0:을} 샀다.", "<듀랑고>", "<듀랑고>를 샀다.")] 56 | public void Test_Filter(string format, object arg0, string expectedResult) 57 | { 58 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 59 | } 60 | 61 | [TestCase("{0:ko:은} {1:ko:로} 불린다.", "나오", "검은사신", "나오는 검은사신으로 불린다.")] 62 | public void Test_Arg2(string format, object arg0, object arg1, string expectedResult) 63 | { 64 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0, arg1)); 65 | } 66 | 67 | [TestCase("{0}{0:ko:-으로}", "돌날", "돌날로")] 68 | [TestCase("{0:-으로}", "마법", "으로")] 69 | public void Test_DashPrefix(string format, object arg0, string expectedResult) 70 | { 71 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 72 | } 73 | 74 | [TestCase("{0:ko(아):{}} 안녕", "나오", "나오야 안녕")] 75 | [TestCase("{0:ko(아):아}", "나오", "아야")] 76 | public void Test_FormattingOption(string format, object arg0, string expectedResult) 77 | { 78 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 79 | } 80 | 81 | [TestCase("{0:ko(은):{}} {1:ko(으로):{}} 불린다.", "나오", "검은사신", "나오는 검은사신으로 불린다.")] 82 | [TestCase("{0:은} {1:으로} 변신 했다!", "밀레시안", "팔라딘", "밀레시안은 팔라딘으로 변신 했다!")] 83 | public void Test_FormattingOption_Arg2(string format, object arg0, object arg1, string expectedResult) 84 | { 85 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0, arg1)); 86 | } 87 | 88 | [TestCase("{0:야}", "친구", "친구야")] 89 | [TestCase("{0:야}", "사랑", "사랑아")] 90 | [TestCase("{0:아}", "사랑", "사랑아")] 91 | [TestCase("{0:여}", "친구", "친구여")] 92 | [TestCase("{0:여}", "사랑", "사랑이여")] 93 | [TestCase("{0:이시여}", "하늘", "하늘이시여")] 94 | [TestCase("{0:이시여}", "바다", "바다시여")] 95 | public void Test_Vocative_Particles(string format, object arg0, string expectedResult) 96 | { 97 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 98 | } 99 | 100 | [TestCase("{0:이다}", "나오", "나오다")] 101 | [TestCase("{0:이다}", "키홀", "키홀이다")] 102 | [TestCase("{0:이에요}", "나오", "나오예요")] 103 | [TestCase("{0:이에요}", "키홀", "키홀이에요")] 104 | [TestCase("{0:입니다}", "나오", "나오입니다")] 105 | [TestCase("{0:입니다}", "키홀", "키홀입니다")] 106 | [TestCase("{0:이다}", "Nao", "Nao(이)다")] 107 | [TestCase("{0:이에요}", "Nao", "Nao(이)에요")] 108 | [TestCase("{0:입니다}", "Nao", "Nao입니다")] 109 | [TestCase("{0:였습니다}", "Nao", "Nao(이)었습니다")] 110 | [TestCase("{0:였습니다}", "키홀", "키홀이었습니다")] 111 | [TestCase("{0:였습니다}", "나오", "나오였습니다")] 112 | [TestCase("{0:이었다}", "나오", "나오였다")] 113 | [TestCase("{0:이었지만}", "나오", "나오였지만")] 114 | [TestCase("{0:이지만}", "나오", "나오지만")] 115 | [TestCase("{0:이지만}", "키홀", "키홀이지만")] 116 | [TestCase("{0:지만}", "나오", "나오지만")] 117 | [TestCase("{0:지만}", "키홀", "키홀이지만")] 118 | [TestCase("{0:다}", "나오", "나오다")] 119 | [TestCase("{0:다}", "키홀", "키홀이다")] 120 | [TestCase("{0:이에요}", "나오", "나오예요")] 121 | [TestCase("{0:이에요}", "키홀", "키홀이에요")] 122 | [TestCase("{0:고}", "나오", "나오고")] 123 | [TestCase("{0:고}", "키홀", "키홀이고")] 124 | [TestCase("{0:고}", "모리안", "모리안이고")] 125 | [TestCase("{0:여서}", "나오", "나오여서")] 126 | [TestCase("{0:여서}", "키홀", "키홀이어서")] 127 | [TestCase("{0:이어서}", "나오", "나오여서")] 128 | [TestCase("{0:이어서}", "키홀", "키홀이어서")] 129 | [TestCase("{0:라고라}?", "키홀", "키홀이라고라?")] 130 | public void Test_Ida(string format, object arg0, string expectedResult) 131 | { 132 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 133 | } 134 | 135 | [TestCase("{0:ko(으로):으로}", "용사", "으로로")] 136 | [TestCase("{0:ko(으로):으로}", "마법", "으로으로")] 137 | [TestCase("{0:ko(으로):{}}", "마법", "마법으로")] 138 | [TestCase("{0:ko(으로):{}}", "나오(Lv.25)", "나오(Lv.25)로")] 139 | [TestCase("{0:ko(으로):{}}", "퍼거(?)스", "퍼거(?)스로")] 140 | [TestCase("{0:ko(으로):{}}", "헬로월드!", "헬로월드!로")] 141 | [TestCase("{0:ko(으로):{}}", "?_?", "?_?(으)로")] 142 | [TestCase("{0:ko(으로):{}}", "마법", "마법으로")] 143 | [TestCase("{0:ko(로서):{}}", "나오", "나오로서")] 144 | [TestCase("{0:ko(로서):{}}", "키홀", "키홀로서")] 145 | [TestCase("{0:ko(로서):{}}", "모리안", "모리안으로서")] 146 | [TestCase("{0:ko(로부터):{}}", "나오", "나오로부터")] 147 | [TestCase("{0:ko(로부터):{}}", "키홀", "키홀로부터")] 148 | [TestCase("{0:ko(로부터):{}}", "모리안", "모리안으로부터")] 149 | public void Test_Euro(string format, object arg0, string expectedResult) 150 | { 151 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 152 | } 153 | 154 | 155 | [TestCase("{0:도}", "나오", "나오도")] 156 | [TestCase("{0:도}", "모리안", "모리안도")] 157 | [TestCase("{0:에서}", "판교", "판교에서")] 158 | [TestCase("{0:에서는}", "판교", "판교에서는")] 159 | [TestCase("{0:께서도}", "선생님", "선생님께서도")] 160 | [TestCase("{0:의}", "나오", "나오의")] 161 | [TestCase("{0:만}", "모리안", "모리안만")] 162 | [TestCase("{0:하고}", "키홀", "키홀하고")] 163 | public void Test_InvariantParticles(string format, object arg0, string expectedResult) 164 | { 165 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 166 | } 167 | 168 | [TestCase("금화 {0:이}", "10", "금화 10이")] 169 | [TestCase("레벨 {0:이}", "999", "레벨 999가")] 170 | [TestCase("금화 {0:이}", 10, "금화 10이")] 171 | [TestCase("레벨 {0:이}", 999, "레벨 999가")] 172 | public void Test_Number(string format, object arg0, string expectedResult) 173 | { 174 | Assert.AreEqual(expectedResult, Smart.Format(format, arg0)); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 6 | // 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 7 | // 이러한 특성 값을 변경하세요. 8 | [assembly: AssemblyTitle("KoreanFormatter.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("KoreanFormatter.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 18 | // 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 19 | // 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. 20 | [assembly: ComVisible(false)] 21 | 22 | // 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. 23 | [assembly: Guid("8ef1a879-19e4-41bb-88c4-ceada2e7d190")] 24 | 25 | // 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. 26 | // 27 | // 주 버전 28 | // 부 버전 29 | // 빌드 번호 30 | // 수정 버전 31 | // 32 | // 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로 33 | // 지정되도록 할 수 있습니다. 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.Tests/SmartFormat.NET-Korean.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8EF1A879-19E4-41BB-88C4-CEADA2E7D190} 8 | Library 9 | Properties 10 | SmartFormatKorean.Tests 11 | SmartFormatKorean.Tests 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 36 | True 37 | 38 | 39 | ..\packages\SmartFormat.NET.1.6.1.0\lib\net40\SmartFormat.dll 40 | True 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5} 63 | SmartFormat.NET-Korean 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.Tests/Utilities/HangulTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using SmartFormat.Utilities; 4 | 5 | namespace KoreanParticleFormatter.Tests.Utilities 6 | { 7 | [TestFixture] 8 | internal class HangulTests 9 | { 10 | private readonly Hangul _hangul = new Hangul(); 11 | 12 | [TestCase('ㅇ', 'ㅏ', 'ㄴ', '안')] 13 | [TestCase('ㄴ', 'ㅕ', 'ㅇ', '녕')] 14 | [TestCase('ㅎ', 'ㅏ', '\0', '하')] 15 | [TestCase('ㅅ', 'ㅔ', '\0', '세')] 16 | [TestCase('ㅇ', 'ㅛ', '\0', '요')] 17 | public void Test_JoinPhonemes(char onset, char nuclues, char coda, char expectedResult) 18 | { 19 | Assert.AreEqual(expectedResult, _hangul.JoinPhonemes(onset, nuclues, coda)); 20 | } 21 | 22 | [TestCase('안', new char[] { 'ㅇ', 'ㅏ', 'ㄴ' })] 23 | [TestCase('녕', new char[] { 'ㄴ', 'ㅕ', 'ㅇ' })] 24 | [TestCase('하', new char[] { 'ㅎ', 'ㅏ', '\0'})] 25 | [TestCase('세', new char[] { 'ㅅ', 'ㅔ', '\0'})] 26 | [TestCase('요', new char[] { 'ㅇ', 'ㅛ', '\0'})] 27 | public void Test_SplitPhonemes(char letter, char[] expectedResult) 28 | { 29 | Assert.AreEqual(expectedResult, _hangul.SplitPhonemes(letter)); 30 | } 31 | 32 | [TestCase("10", '십')] 33 | [TestCase("200", '백')] 34 | [TestCase("3000", '천')] 35 | [TestCase("40000", '만')] 36 | [TestCase("500000", '만')] 37 | [TestCase("6000000", '만')] 38 | [TestCase("70000000", '만')] 39 | [TestCase("800000000", '억')] 40 | [TestCase("9000000000", '억')] 41 | [TestCase("1000001000", '천')] 42 | [TestCase("2000003020", '십')] 43 | [TestCase("0", '영')] 44 | [TestCase("1", '일')] 45 | [TestCase("2", '이')] 46 | [TestCase("3", '삼')] 47 | [TestCase("4", '사')] 48 | [TestCase("5", '오')] 49 | [TestCase("6", '육')] 50 | [TestCase("7", '칠')] 51 | [TestCase("8", '팔')] 52 | [TestCase("9", '구')] 53 | public void Test_LastHangulCharacterFromNumber(string numberString, char expectedResult) 54 | { 55 | Assert.AreEqual(expectedResult, _hangul.PickLastHangulCharacterFromNumber(numberString)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartFormat.NET-Korean", "SmartFormat.NET-Korean\SmartFormat.NET-Korean.csproj", "{F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartFormat.NET-Korean.Tests", "SmartFormat.NET-Korean.Tests\SmartFormat.NET-Korean.Tests.csproj", "{8EF1A879-19E4-41BB-88C4-CEADA2E7D190}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5} = {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5} 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {8EF1A879-19E4-41BB-88C4-CEADA2E7D190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {8EF1A879-19E4-41BB-88C4-CEADA2E7D190}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {8EF1A879-19E4-41BB-88C4-CEADA2E7D190}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {8EF1A879-19E4-41BB-88C4-CEADA2E7D190}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/Extensions/KoreanFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Text.RegularExpressions; 5 | using SmartFormat.Core.Extensions; 6 | using SmartFormat.Utilities; 7 | 8 | namespace SmartFormat.Extensions 9 | { 10 | public class KoreanFormatter : IFormatter 11 | { 12 | private string[] names = { "ko", "" }; 13 | public string[] Names { get { return names; } set { names = value; } } 14 | private readonly SmartFormatter _formatter = null; 15 | private Hangul _hangul = new Hangul(); 16 | 17 | private readonly Regex _filterPattern = new Regex(@"\(.*[^\(]?\)|[!@#$%^$*?,.:;'""\[\]{}<>]+"); 18 | 19 | internal class SyllableInfo 20 | { 21 | /// 22 | /// Evaluate the syllable information of Hangul character. 23 | /// 24 | /// `Hangul` unicode range: 가(U+AC00) ~ 힣(U+D7A3) 25 | /// 26 | /// A `Hangul` character. 27 | public SyllableInfo(char hangulChar) 28 | { 29 | int jongsungExpr = (hangulChar - '가') % 28; 30 | HasCoda = jongsungExpr != 0; 31 | HasRieulCoda = jongsungExpr == 8; 32 | System.Collections.Generic.Dictionary a; 33 | } 34 | 35 | // in Korean, `Coda` means final position of syllable 36 | public bool HasCoda; 37 | public bool HasRieulCoda; 38 | } 39 | 40 | // Particle phonology 41 | private readonly string[] _simpleParticles = 42 | { 43 | "을를", "아야", "이가", "은는", "과와" 44 | }; 45 | 46 | private readonly string[] _idaExcepts = 47 | { 48 | "여", "시여" 49 | }; 50 | 51 | private readonly Regex _invariantParticlePattern = new Regex(@"^((의|도|만|보다|부터|까지|마저|조차)$|에|께|하)"); 52 | 53 | private readonly Regex _euroPattern = new Regex(@"^(으|\(으\))?로"); 54 | private readonly Regex _idaPrefixPattern = new Regex(@"^이|\(이\)"); 55 | 56 | public KoreanFormatter(SmartFormatter formatter) 57 | { 58 | _formatter = formatter; 59 | } 60 | 61 | public bool TryEvaluateFormat(IFormattingInfo formattingInfo) 62 | { 63 | if (formattingInfo.Format == null || string.IsNullOrEmpty(formattingInfo.Format.RawText)) 64 | { 65 | return false; 66 | } 67 | 68 | string currentValue = null; 69 | if (formattingInfo.CurrentValue is string) 70 | { 71 | currentValue = (string) formattingInfo.CurrentValue; 72 | } 73 | else 74 | { 75 | currentValue = formattingInfo.CurrentValue.ToString(); 76 | } 77 | 78 | SyllableInfo syllableInfo = EvaluateSyllable(currentValue); 79 | 80 | var format = formattingInfo.FormatterOptions; 81 | bool implicitly = string.IsNullOrEmpty(format); 82 | bool onlyParticle = false; 83 | if (implicitly) 84 | { 85 | format = formattingInfo.Format.RawText; 86 | onlyParticle = format[0] == '-'; 87 | if (onlyParticle) 88 | { 89 | format = format.Substring(1); 90 | } 91 | } 92 | 93 | string particle = ParticleConverter(format, syllableInfo); 94 | if (string.IsNullOrEmpty(particle)) 95 | { 96 | return false; 97 | } 98 | 99 | if (onlyParticle) 100 | { 101 | formattingInfo.Write(particle); 102 | } 103 | else 104 | { 105 | if (implicitly) 106 | { 107 | formattingInfo.Write(currentValue); 108 | formattingInfo.Write(particle); 109 | } 110 | else 111 | { 112 | var newFormat = this._formatter.Parser.ParseFormat(formattingInfo.Format.RawText + particle); 113 | formattingInfo.Write(newFormat, formattingInfo.CurrentValue); 114 | } 115 | } 116 | return true; 117 | } 118 | 119 | private bool TryParseIda(string format, SyllableInfo syllableInfo, out string result) 120 | { 121 | // remove "이" or "(이)" prefix 122 | var suffix = _idaPrefixPattern.Replace(format, ""); 123 | if (string.IsNullOrEmpty(suffix)) 124 | { 125 | result = null; 126 | return false; 127 | } 128 | 129 | if (!_idaExcepts.Contains(suffix)) 130 | { 131 | var phonemes = _hangul.SplitPhonemes(suffix[0]); 132 | if (phonemes == null) 133 | { 134 | result = null; 135 | return false; 136 | } 137 | var onset = phonemes[0]; 138 | var nucleus = phonemes[1]; 139 | var coda = phonemes[2]; 140 | if (onset == 'ㅇ') 141 | { 142 | if (nucleus == 'ㅣ') 143 | { 144 | // No allomorphs when a form starts with "이" and has a coda. 145 | result = suffix; 146 | return true; 147 | } 148 | 149 | bool hasCoda = (syllableInfo == null || syllableInfo.HasCoda); 150 | char nextNucleus = '\0'; 151 | if (!hasCoda && (nucleus == 'ㅓ' || nucleus == 'ㅔ')) 152 | { 153 | nextNucleus = (nucleus == 'ㅓ') ? 'ㅕ' : 'ㅖ'; 154 | } 155 | else if (hasCoda && (nucleus == 'ㅕ' || nucleus == 'ㅖ')) 156 | { 157 | nextNucleus = (nucleus == 'ㅕ') ? 'ㅓ' : 'ㅔ'; 158 | } 159 | 160 | if (nextNucleus != '\0') 161 | { 162 | var nextLetter = _hangul.JoinPhonemes('ㅇ', nextNucleus, coda); 163 | suffix = nextLetter + suffix.Substring(1); 164 | } 165 | } 166 | } 167 | 168 | if (syllableInfo == null) 169 | { 170 | result = "(이)" + suffix; 171 | } 172 | else 173 | { 174 | result = syllableInfo.HasCoda ? '이' + suffix : suffix; 175 | } 176 | return true; 177 | } 178 | 179 | private SyllableInfo EvaluateSyllable(string value) 180 | { 181 | var filteredValue = _filterPattern.Replace(value, ""); 182 | 183 | if (string.IsNullOrEmpty(filteredValue)) 184 | { 185 | return null; 186 | } 187 | 188 | var lastChar = filteredValue[filteredValue.Length - 1]; 189 | 190 | if (Hangul.IsNumericChar(lastChar)) 191 | { 192 | lastChar = _hangul.PickLastHangulCharacterFromNumber(value); 193 | } 194 | 195 | // `Hangul` unicode range: 가(U+AC00) ~ 힣(U+D7A3) 196 | if (!(('가' <= lastChar) && (lastChar <= '힣'))) 197 | { 198 | return null; 199 | } 200 | 201 | return new SyllableInfo(lastChar); 202 | } 203 | 204 | private string ParticleConverter(string josaFormat, SyllableInfo syllableInfo) 205 | { 206 | if (josaFormat.Length == 1) 207 | { 208 | var josa = josaFormat[0]; 209 | foreach (var j in _simpleParticles) 210 | { 211 | if (josa == j[0] || josa == j[1]) 212 | { 213 | if (syllableInfo == null) 214 | { 215 | return string.Format("{0}({1})", j[0], j[1]); 216 | } 217 | 218 | int toIndex = syllableInfo.HasCoda ? 0 : 1; 219 | return j[toIndex].ToString(); 220 | } 221 | } 222 | } 223 | 224 | var euroMatch = _euroPattern.Match(josaFormat); 225 | if (euroMatch.Success) 226 | { 227 | // remove '으로' prefix 228 | var suffixString = josaFormat.Substring(euroMatch.Value.Length); 229 | if (syllableInfo == null) 230 | { 231 | return string.Format("(으)로{0}", suffixString); 232 | } 233 | 234 | return (!syllableInfo.HasCoda || syllableInfo.HasRieulCoda) ? '로' + suffixString: "으로" + suffixString; 235 | } 236 | 237 | if (_invariantParticlePattern.IsMatch(josaFormat)) 238 | { 239 | return josaFormat; 240 | } 241 | 242 | string idaResult = null; 243 | if (TryParseIda(josaFormat, syllableInfo, out idaResult)) 244 | { 245 | return idaResult; 246 | } 247 | 248 | return null; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 6 | // 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 7 | // 이러한 특성 값을 변경하세요. 8 | [assembly: AssemblyTitle("KoreanFormatter")] 9 | [assembly: AssemblyDescription("Korean Formatter for SmartFormat.Net")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("What! Studio in Nexon Korea")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("Copyright 2016 Nexon Korea")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 18 | // 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 19 | // 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. 20 | [assembly: ComVisible(false)] 21 | 22 | // 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. 23 | [assembly: Guid("f00ea807-2975-41f6-ad8f-c1bce7a3b4f5")] 24 | 25 | // 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. 26 | // 27 | // 주 버전 28 | // 부 버전 29 | // 빌드 번호 30 | // 수정 버전 31 | // 32 | // 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로 33 | // 지정되도록 할 수 있습니다. 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.2")] 36 | [assembly: AssemblyFileVersion("1.0.2")] 37 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/SmartFormat.NET-Korean.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F00EA807-2975-41F6-AD8F-C1BCE7A3B4F5} 8 | Library 9 | Properties 10 | SmartFormatKorean 11 | SmartFormatKorean 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\SmartFormat.NET.1.6.1.0\lib\net40\SmartFormat.dll 36 | True 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | $(ProjectDir)nuget pack $(ProjectPath) -Prop Configuration=Release -IncludeReferencedProjects 57 | 58 | 65 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/SmartFormat.NET-Korean.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SmartFormat.NET-Korean 5 | $version$ 6 | SmartFormat.NET-Korean 7 | $author$ 8 | $author$ 9 | https://github.com/what-studio/SmartFormat.Net-Korean/blob/master/LICENSE 10 | https://github.com/what-studio/SmartFormat.Net-Korean 11 | false 12 | $description$ 13 | Copyright 2016 Nexon 14 | 15 | 16 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/Utilities/Hangul.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Security.Policy; 6 | using System.Text; 7 | 8 | namespace SmartFormat.Utilities 9 | { 10 | /// 11 | /// Manipulates Hangul letters. 12 | /// 13 | public class Hangul 14 | { 15 | private readonly char[] _onsets = 16 | { 17 | 'ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ', 18 | 'ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' 19 | }; 20 | 21 | private readonly char[] _nucleuses = 22 | { 23 | 'ㅏ','ㅐ','ㅑ','ㅒ','ㅓ','ㅔ','ㅕ','ㅖ','ㅗ','ㅘ','ㅙ','ㅚ','ㅛ', 24 | 'ㅜ','ㅝ','ㅞ','ㅟ','ㅠ','ㅡ','ㅢ','ㅣ' 25 | }; 26 | 27 | private readonly char[] _codas = 28 | { 29 | '\0', 'ㄱ','ㄲ','ㄳ','ㄴ','ㄵ','ㄶ','ㄷ','ㄹ','ㄺ','ㄻ','ㄼ','ㄽ','ㄾ', 30 | 'ㄿ','ㅀ','ㅁ','ㅂ','ㅄ','ㅅ','ㅆ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' 31 | }; 32 | 33 | private readonly string _hangulDigits = "영일이삼사오육칠팔구"; 34 | private readonly Dictionary _hangul10Digits = new Dictionary 35 | { 36 | {1, '십'}, {2, '백'}, {3, '천'}, {4, '만'}, 37 | {8, '억'}, {12, '조'}, {16, '경'}, {20, '해'}, 38 | {24, '자'}, {28, '양'}, {32, '구'}, {36, '간'}, 39 | // 52: 항하사 40 | {40, '정'}, {44, '재'}, {48, '극'}, {52, '사'}, 41 | // 56: 아승기, 60: 나유타, 64: 불가사의, 68: 무량대수 42 | {56, '기'}, {60, '타'}, {64, '의'}, {68, '수'}, 43 | {72, '겁'}, {76, '업'} 44 | }; 45 | 46 | 47 | public char JoinPhonemes(char onset, char nucleus, char coda='\0') 48 | { 49 | return (char)((Array.IndexOf(_onsets, onset) * _nucleuses.Length + Array.IndexOf(_nucleuses, nucleus)) * _codas.Length + Array.IndexOf(_codas, coda) + '가'); 50 | } 51 | 52 | public char[] SplitPhonemes(char letter, bool onset = true, bool nucleus = true, bool coda = true) 53 | { 54 | if (!(('가' <= letter) && (letter <= '힣'))) 55 | { 56 | return null; 57 | } 58 | 59 | var phonemes = new char[3]; 60 | 61 | var offset = letter - '가'; 62 | if (onset) 63 | { 64 | phonemes[0] = _onsets[offset / (_nucleuses.Length * _codas.Length)]; 65 | } 66 | if (nucleus) 67 | { 68 | phonemes[1] = _nucleuses[(offset / _codas.Length) % _nucleuses.Length]; 69 | } 70 | if (coda) 71 | { 72 | phonemes[2] = _codas[offset % _codas.Length]; 73 | } 74 | return phonemes; 75 | } 76 | 77 | public static bool IsNumericChar(char c) 78 | { 79 | return (('0' <= c) && (c <= '9')); 80 | } 81 | 82 | public char PickLastHangulCharacterFromNumber(string value) 83 | { 84 | // finds the last non-zero digit. 85 | int startIndex = value.Length; 86 | for (int i = value.Length-1; i >= 0; i--) 87 | { 88 | if (!IsNumericChar(value[i])) 89 | { 90 | break; 91 | } 92 | startIndex = i; 93 | if (value[i] != '0') 94 | { 95 | break; 96 | } 97 | } 98 | 99 | int numberLength = value.Length - startIndex; 100 | if (numberLength == 1) 101 | { 102 | int toInt = (value[startIndex] - '0'); 103 | return _hangulDigits[toInt]; 104 | } 105 | 106 | int findKey = -1; 107 | foreach (var length in _hangul10Digits.Keys) 108 | { 109 | if (length == numberLength - 1) 110 | { 111 | return _hangul10Digits[length]; 112 | } 113 | if (length > numberLength - 1) 114 | { 115 | break; 116 | } 117 | findKey = length; 118 | } 119 | 120 | // can't found key 121 | if (findKey == -1) 122 | { 123 | return _hangul10Digits.Last().Value; 124 | } 125 | 126 | return _hangul10Digits[findKey]; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/what-studio/SmartFormat.NET-Korean/cd149be3a683a2ef49fbef44e31308d39c3ca375/SmartFormat.NET-Korean/nuget.exe -------------------------------------------------------------------------------- /SmartFormat.NET-Korean/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /demo_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/what-studio/SmartFormat.NET-Korean/cd149be3a683a2ef49fbef44e31308d39c3ca375/demo_image.png --------------------------------------------------------------------------------