feat: handle option to handle digits and punctuations pronunciation
This commit is contained in:
		
							parent
							
								
									6db5c32bcd
								
							
						
					
					
						commit
						6cfaf63502
					
				
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,6 +1,145 @@ | |||||||
| import { describe, expect, it } from 'vitest'; | import { describe, expect, it } from 'vitest'; | ||||||
| import { textToNatoAlphabet } from './text-to-nato-alphabet.service'; | import { textToNatoAlphabet } from './text-to-nato-alphabet.service'; | ||||||
| import natoTests from './nato.test.data.json'; | 
 | ||||||
|  | const TestCase_TestYW = 'TesYW 123 ? = !@'; | ||||||
|  | const TestCase_TestYJ = 'TesYj 123 ? = !@'; | ||||||
|  | const natoTests = [ | ||||||
|  |   { | ||||||
|  |     lang: '(International)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'TANGO echo sierra YANKEE WHISKEY (SPACE) (digit 1) (digit 2) (digit 3) (SPACE) (punctuation ?) (SPACE) (punctuation =) (SPACE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(France)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'THÉRÈSE eugène suzanne YVONNE WILLIAM (ESPACE) (digit 1) (digit 2) (digit 3) (ESPACE) (punctuation ?) (ESPACE) (punctuation =) (ESPACE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Belgium)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'TELEFOON emiel sofie YVONNE WATERLOO (ESPACE) (digit 1) (digit 2) (digit 3) (ESPACE) (punctuation ?) (ESPACE) (punctuation =) (ESPACE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Switzerland)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'THÉRÈSE émile suzanne (Y) WILLIAM (ESPACE) (digit 1) (digit 2) (digit 3) (ESPACE) (punctuation ?) (ESPACE) (punctuation =) (ESPACE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Québec)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'THOMAS édouard samuel (Y) WILLIAM (ESPACE) (digit 1) (digit 2) (digit 3) (ESPACE) (punctuation ?) (ESPACE) (punctuation =) (ESPACE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Germany, 2022)', | ||||||
|  |     input: 'TesYü 123 ? = !@', | ||||||
|  |     output: 'TÜBINGEN essen salzwedel YPSILON umlaut-unna (LEERZEICHEN) (digit 1) (digit 2) (digit 3) (LEERZEICHEN) (punctuation ?) (LEERZEICHEN) (punctuation =) (LEERZEICHEN) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Austria)', | ||||||
|  |     input: TestCase_TestYW, | ||||||
|  |     output: 'THEODOR emil samuel/siegfried YPSILON WILHELM (LEERZEICHEN) (digit 1) (digit 2) (digit 3) (LEERZEICHEN) (punctuation ?) (LEERZEICHEN) (punctuation =) (LEERZEICHEN) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Germany, informal, 2022)', | ||||||
|  |     input: 'TesYü 123 ? = !@', | ||||||
|  |     output: 'THEODOR emil samuel YPSILON überfluss (LEERZEICHEN) (digit 1) (digit 2) (digit 3) (LEERZEICHEN) (punctuation ?) (LEERZEICHEN) (punctuation =) (LEERZEICHEN) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Netherlands)', | ||||||
|  |     input: 'TesY huis rij 123 ? = !@', | ||||||
|  |     output: 'THEODOR eduard simon YPSILON (SPATIE) hendrik utrecht izaak simon (SPATIE) richard/rudolf ijmuiden/ijsbrand (SPATIE) (digit 1) (digit 2) (digit 3) (SPATIE) (punctuation ?) (SPATIE) (punctuation =) (SPATIE) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Italian', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TORINO empoli savona YORK, YOGURT jolly/juventus (SPAZIO) (digit 1) (digit 2) (digit 3) (SPAZIO) (punctuation ?) (SPAZIO) (punctuation =) (SPAZIO) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Spanish', | ||||||
|  |     input: 'TesYñj 123 ? = !@', | ||||||
|  |     output: 'TOLEDO españa sábado YOLANDA ñoño josé (ESPACIO) (digit 1) (digit 2) (digit 3) (ESPACIO) (punctuation ?) (ESPACIO) (punctuation =) (ESPACIO) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Brazil)', | ||||||
|  |     input: 'TesYÇj 123 ? = !@', | ||||||
|  |     output: 'TATU estrela saci YOLANDA (Ç) josé (ESPAÇO) (digit 1) (digit 2) (digit 3) (ESPAÇO) (punctuation ?) (ESPAÇO) (punctuation =) (ESPAÇO) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: '(Portugal)', | ||||||
|  |     input: 'TesYúj 123 ? = !@', | ||||||
|  |     output: 'TAVIRA évora setúbal YORK (ú) josé (ESPAÇO) (digit 1) (digit 2) (digit 3) (ESPAÇO) (punctuation ?) (ESPAÇO) (punctuation =) (ESPAÇO) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Swedish', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TORE erik sigurd YNGVE johan (UTRYMME) (digit 1) (digit 2) (digit 3) (UTRYMME) (punctuation ?) (UTRYMME) (punctuation =) (UTRYMME) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Danish', | ||||||
|  |     input: 'TesÆYj 123 ? = !@', | ||||||
|  |     output: 'THEODOR erik søren ÆGIR YRSA johan (MELLEMRUMSTEGN) (digit 1) (digit 2) (digit 3) (MELLEMRUMSTEGN) (punctuation ?) (MELLEMRUMSTEGN) (punctuation =) (MELLEMRUMSTEGN) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Norwegian', | ||||||
|  |     input: 'TesYÅj 123 ? = !@', | ||||||
|  |     output: 'TEODOR edith sigrid YNGLING ÅSE johan (MELLOMROMSTEGN) (digit 1) (digit 2) (digit 3) (MELLOMROMSTEGN) (punctuation ?) (MELLOMROMSTEGN) (punctuation =) (MELLOMROMSTEGN) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Finnish', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TYYNE eemeli sakari YRJÖ jussi (VÄLILYÖNTI) (digit 1) (digit 2) (digit 3) (VÄLILYÖNTI) (punctuation ?) (VÄLILYÖNTI) (punctuation =) (VÄLILYÖNTI) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Turkish', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TOKAT edirne sinop YOZGAT jandarma (BOŞLUK) (digit 1) (digit 2) (digit 3) (BOŞLUK) (punctuation ?) (BOŞLUK) (punctuation =) (BOŞLUK) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Romanian', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TUDOR elena sandu I-GREC jean (SPAȚIU) (digit 1) (digit 2) (digit 3) (SPAȚIU) (punctuation ?) (SPAȚIU) (punctuation =) (SPAȚIU) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Czech', | ||||||
|  |     input: TestCase_TestYJ, | ||||||
|  |     output: 'TOMÁŠ emil svatopluk YPSILON josef (PROSTOROVÝ) (digit 1) (digit 2) (digit 3) (PROSTOROVÝ) (punctuation ?) (PROSTOROVÝ) (punctuation =) (PROSTOROVÝ) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Yugoslav', | ||||||
|  |     input: 'TesYČj 123 ? = !@', | ||||||
|  |     output: 'TUZLA evropa skopje IPSILON ČAČAK jadran (PRIESTOROVÝ) (digit 1) (digit 2) (digit 3) (PRIESTOROVÝ) (punctuation ?) (PRIESTOROVÝ) (punctuation =) (PRIESTOROVÝ) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Serbian', | ||||||
|  |     input: 'TesYČj 123 ? = !@', | ||||||
|  |     output: 'TIMOK evropa sava IPSILON ČAČAK jadran (PROSTORNI) (digit 1) (digit 2) (digit 3) (PROSTORNI) (punctuation ?) (PROSTORNI) (punctuation =) (PROSTORNI) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Slovene', | ||||||
|  |     input: 'TesYČj 123 ? = !@', | ||||||
|  |     output: 'TRIGLAV evropa soča IPSILON ČATEŽ jadran (PRESLEDKA) (digit 1) (digit 2) (digit 3) (PRESLEDKA) (punctuation ?) (PRESLEDKA) (punctuation =) (PRESLEDKA) (punctuation !) (punctuation @)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Russian', | ||||||
|  |     input: 'Зинаида !.?', | ||||||
|  |     output: 'ЗИНАИДА иван николай анна иван дмитрий анна ( ) (punctuation !) (punctuation .) (punctuation ?)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Korean', | ||||||
|  |     input: '안녕하세요 여러분', | ||||||
|  |     output: '잉어 아버지 나폴리 나폴리 연못 잉어 한강 아버지 서울 엑스레이 잉어 요지경 ( ) 잉어 연못 로마 어머니 바가지 우편 나폴리', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Greek', | ||||||
|  |     input: 'τίγρης !?', | ||||||
|  |     output: 'τίγρης (ί) γαλή ρήγας ηρώ σοφός ( ) (punctuation !) (punctuation ?)', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     lang: 'Japanese', | ||||||
|  |     input: '数字のひと おしまいのン ?:', | ||||||
|  |     output: '(数) (字) 野原のノ 飛行機のヒ 東京のト ( ) 大阪のオ 新聞のシ マッチのマ いろはのイ 野原のノ おしまいのン ( ) (punctuation ?) (punctuation :)', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
| 
 | 
 | ||||||
| describe('text-to-nato', () => { | describe('text-to-nato', () => { | ||||||
|   it('Convert text to NATO', async () => { |   it('Convert text to NATO', async () => { | ||||||
| @ -9,4 +148,13 @@ describe('text-to-nato', () => { | |||||||
|       expect(textToNatoAlphabet({ text: input, langOrCountry: lang })).to.equal(output); |       expect(textToNatoAlphabet({ text: input, langOrCountry: lang })).to.equal(output); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   it('Convert text to NATO (includes punctuations and digits names)', async () => { | ||||||
|  |     expect(textToNatoAlphabet({ text: 'a 1 2 3 ! % ? ;', langOrCountry: '(France)', useDigitsNames: true, usePunctuationsNames: true })).to.equal( | ||||||
|  |       'anatole (ESPACE) {digit 1 => un} (ESPACE) {digit 2 => deux} (ESPACE) {digit 3 => trois} (ESPACE) {punctuation ! => point d\'exclamation} (ESPACE) {punctuation % => pour cent} (ESPACE) {punctuation ? => point d\'interrogation} (ESPACE) {punctuation ; => point-virgule}'); | ||||||
|  |     expect(textToNatoAlphabet({ text: 'и 1 2 3 ! % ? ;', langOrCountry: 'Russian', useDigitsNames: true, usePunctuationsNames: true })).to.equal( | ||||||
|  |       'иван ( ) {digit 1 => один (odin)} ( ) {digit 2 => два (dva)} ( ) {digit 3 => три (tri)} ( ) {punctuation ! => восклицательный знак (vosklitsatel\'nyy znak)} ( ) {punctuation % => процент (protsent)} ( ) {punctuation ? => вопросительный знак (voprositel\'nyy znak)} ( ) {punctuation ; => точка с запятой (tochka s zapyatoy)}'); | ||||||
|  |     expect(textToNatoAlphabet({ text: 'TesYj 1 2 3 ! % ? ;', langOrCountry: 'Swedish', useDigitsNames: true, usePunctuationsNames: true })).to.equal( | ||||||
|  |       'TORE erik sigurd YNGVE johan (UTRYMME) {digit 1 => ett} (UTRYMME) {digit 2 => två} (UTRYMME) {digit 3 => tre} (UTRYMME) {punctuation ! => utropstecken} (UTRYMME) {punctuation % => procent} (UTRYMME) {punctuation ? => frågetecken} (UTRYMME) {punctuation ; => semikolon}'); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,17 +1,46 @@ | |||||||
| import hangul from 'korean-unpacker'; | import hangul from 'korean-unpacker'; | ||||||
| import allAlphabets from './nato.alphabets.json'; | import allAlphabets from './nato.alphabets.json'; | ||||||
| 
 | 
 | ||||||
| type AllALphabetsKeys = keyof typeof allAlphabets[0]; | type AllAlphabetsKeys = keyof typeof allAlphabets[0]; | ||||||
| 
 | 
 | ||||||
| export { textToNatoAlphabet }; | export { textToNatoAlphabet }; | ||||||
| 
 | 
 | ||||||
| function textToNatoAlphabet({ text, langOrCountry = '(International)' }: { text: string; langOrCountry: string }) { | function isPunctuation(char: string) { | ||||||
|  |   const punctuations = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'; | ||||||
|  |   return punctuations.includes(char); | ||||||
|  | } | ||||||
|  | function isDigit(char: string) { | ||||||
|  |   const digits = '0123456789'; | ||||||
|  |   return digits.includes(char); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function escapeRegExp(string: string) { | ||||||
|  |   return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function textToNatoAlphabet({ | ||||||
|  |   text, langOrCountry = '(International)', | ||||||
|  |   useDigitsNames = false, usePunctuationsNames = false, | ||||||
|  | }: { | ||||||
|  |   text: string | ||||||
|  |   langOrCountry: string | ||||||
|  |   useDigitsNames?: boolean | ||||||
|  |   usePunctuationsNames?: boolean | ||||||
|  | }) { | ||||||
|  |   const getNatoWord = (searchChar: string) => { | ||||||
|  |     const alphabetLetter = allAlphabets.find(letter => letter.Letter === searchChar); | ||||||
|  |     if (alphabetLetter && alphabetLetter[langOrCountry as AllAlphabetsKeys]) { | ||||||
|  |       return alphabetLetter[langOrCountry as AllAlphabetsKeys] || ''; | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   const charRegex = new RegExp( |   const charRegex = new RegExp( | ||||||
|     `(${ |     `(${ | ||||||
|         allAlphabets |         allAlphabets | ||||||
|         .sort((a, b) => b.Letter.length - a.Letter.length) |         .sort((a, b) => b.Letter.length - a.Letter.length) | ||||||
|         .filter(a => a[langOrCountry as AllALphabetsKeys]) |         .filter(a => a[langOrCountry as AllAlphabetsKeys]) | ||||||
|         .map(a => a.Letter) |         .map(a => escapeRegExp(a.Letter)) | ||||||
|         .join('|') |         .join('|') | ||||||
|         }|.)`,
 |         }|.)`,
 | ||||||
|     'gi'); |     'gi'); | ||||||
| @ -22,9 +51,26 @@ function textToNatoAlphabet({ text, langOrCountry = '(International)' }: { text: | |||||||
|       (character) => { |       (character) => { | ||||||
|         const searchChar = character.toUpperCase(); |         const searchChar = character.toUpperCase(); | ||||||
|         const isUpper = character[0].toUpperCase() === character[0]; |         const isUpper = character[0].toUpperCase() === character[0]; | ||||||
|         const alphabetLetter = allAlphabets.find(letter => letter.Letter === searchChar); |         const natoWord = getNatoWord(searchChar); | ||||||
|         if (alphabetLetter && alphabetLetter[langOrCountry as AllALphabetsKeys]) { | 
 | ||||||
|           const natoWord = alphabetLetter[langOrCountry as AllALphabetsKeys] || ''; |         if (isDigit(searchChar)) { | ||||||
|  |           if (useDigitsNames) { | ||||||
|  |             return ` {digit ${searchChar} => ${natoWord}}`; | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             return ` (digit ${character})`; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (isPunctuation(searchChar)) { | ||||||
|  |           if (usePunctuationsNames) { | ||||||
|  |             return ` {punctuation ${searchChar} => ${natoWord}}`; | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             return ` (punctuation ${character})`; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (natoWord) { | ||||||
|           return ` ${isUpper ? natoWord.toUpperCase() : natoWord.toLowerCase()}`; |           return ` ${isUpper ? natoWord.toUpperCase() : natoWord.toLowerCase()}`; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,8 @@ import { useCopy } from '@/composable/copy'; | |||||||
| 
 | 
 | ||||||
| const lang = useStorage('text-to-nato:lang', '(International)'); | const lang = useStorage('text-to-nato:lang', '(International)'); | ||||||
| const input = ref(''); | const input = ref(''); | ||||||
|  | const useDigitsNames = useStorage('text-to-nato:digits', false); | ||||||
|  | const usePunctuationsNames = useStorage('text-to-nato:puncts', false); | ||||||
| const natoText = computed(() => textToNatoAlphabet({ text: input.value, langOrCountry: lang.value })); | const natoText = computed(() => textToNatoAlphabet({ text: input.value, langOrCountry: lang.value })); | ||||||
| const { copy } = useCopy({ source: natoText, text: 'NATO alphabet string copied.' }); | const { copy } = useCopy({ source: natoText, text: 'NATO alphabet string copied.' }); | ||||||
| </script> | </script> | ||||||
| @ -17,6 +19,15 @@ const { copy } = useCopy({ source: natoText, text: 'NATO alphabet string copied. | |||||||
|       searchable |       searchable | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|  |     <div flex justify-center> | ||||||
|  |       <n-form-item label="Use digits pronunciation"> | ||||||
|  |         <n-checkbox v-model:checked="usePunctuationsNames" /> | ||||||
|  |       </n-form-item> | ||||||
|  |       <n-form-item label="Use punctuations pronunciation"> | ||||||
|  |         <n-checkbox v-model:checked="useDigitsNames" /> | ||||||
|  |       </n-form-item> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|     <c-input-text |     <c-input-text | ||||||
|       v-model:value="input" |       v-model:value="input" | ||||||
|       label="Your text to convert to NATO phonetic alphabet" |       label="Your text to convert to NATO phonetic alphabet" | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user