greek-name-correction 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # GreekNameCorrection
2
2
 
3
- A powerful, zero-dependency Node.js library for correcting, formatting, and validating Greek names with advanced features including transliteration, genitive conversion, and intelligent name processing.
3
+ A powerful, zero-dependency library for correcting, formatting, and validating Greek names with advanced features including transliteration, genitive conversion, and intelligent name processing. Works seamlessly in both Node.js and browser environments.
4
4
 
5
5
  ![NPM Version](https://img.shields.io/npm/v/greek-name-correction)
6
6
  ![License](https://img.shields.io/npm/l/greek-name-correction)
7
7
  ![Node Version](https://img.shields.io/node/v/greek-name-correction)
8
+ ![Codecov](https://codecov.io/gh/sraftopo/greek-name-correction/branch/main/graph/badge.svg)
8
9
 
9
10
  ## Features
10
11
 
@@ -14,7 +15,7 @@ A powerful, zero-dependency Node.js library for correcting, formatting, and vali
14
15
  📝 **Smart Formatting** - Proper capitalization and syntax
15
16
  👔 **Title Support** - Handles Greek honorifics (Δρ., Καθ., etc.)
16
17
  🎩 **Auto Title Addition** - Automatically adds general titles (Κ. for men, Κα for women)
17
- 🔀 **Case Conversion** - Genitive, vocative, and accusative forms
18
+ 🔀 **Case Conversion** - Genitive, vocative (κλητική), and accusative (αιτιατική); distinct rules for names like Πρόδρομος → Πρόδρομο / Πρόδρομε
18
19
  🎯 **Gender Detection** - Identifies gender from name endings
19
20
  📊 **Statistics** - Comprehensive name analysis
20
21
  🔍 **Diminutive Detection** - Recognizes nickname patterns
@@ -120,10 +121,14 @@ greek-name-correction -name "Μαρία Κωνσταντίνου" -detectGender
120
121
  ### Examples
121
122
 
122
123
  ```bash
123
- # Vocative case conversion
124
- greek-name-correction -name "Γιώργος Παπαδόπουλος" -convertToCase vocative
124
+ # Vocative (addressing): Πρόδρομος → Πρόδρομε
125
+ greek-name-correction -name "Πρόδρομος" -convertToCase vocative
126
+
127
+ # Accusative (direct object): Πρόδρομος → Πρόδρομο
128
+ greek-name-correction -name "Πρόδρομος" -convertToCase accusative
125
129
 
126
- # Accusative case conversion
130
+ # Full name examples
131
+ greek-name-correction -name "Γιώργος Παπαδόπουλος" -convertToCase vocative
127
132
  greek-name-correction -name "Δημήτρης Νικολάου" -convertToCase accusative
128
133
 
129
134
  # Transliteration
@@ -216,7 +221,10 @@ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
216
221
  // }
217
222
  ```
218
223
 
219
- ### Vocative Case Conversion
224
+ ### Vocative Case Conversion (Κλητική)
225
+
226
+ Use `convertToCase: 'vocative'` when **addressing** someone directly (e.g. «Γεια σου, …», «Αγαπητέ …»).
227
+
220
228
  ```javascript
221
229
  // Convert to vocative case (for addressing someone)
222
230
  GreekNameCorrection('Γιώργος Παπαδόπουλος', {
@@ -224,6 +232,13 @@ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
224
232
  });
225
233
  // → "Γιώργο Παπαδόπουλο"
226
234
 
235
+ // Longer first names often form vocative in -ε
236
+ GreekNameCorrection('Στέφανος', { convertToCase: 'vocative' });
237
+ // → "Στέφανε"
238
+
239
+ GreekNameCorrection('Πρόδρομος', { convertToCase: 'vocative' });
240
+ // → "Πρόδρομε"
241
+
227
242
  // With preserveOriginal to get both forms
228
243
  GreekNameCorrection('Γιάννης Αλεξίου', {
229
244
  convertToCase: 'vocative',
@@ -241,7 +256,23 @@ GreekNameCorrection('Μαρία Κωνσταντίνου', {
241
256
  // → "Μαρία Κωνσταντίνου"
242
257
  ```
243
258
 
244
- ### Accusative Case Conversion
259
+ **Masculine names ending in -ος (vocative)** depend on the name and position (first name vs surname):
260
+
261
+ | Pattern | Examples (nominative → vocative) |
262
+ |--------|-----------------------------------|
263
+ | Common short first names → **-ο** | Γιώργος → Γιώργο, Νίκος → Νίκο, Πέτρος → Πέτρο |
264
+ | Listed longer first names → **-ε** | Στέφανος → Στέφανε, Κωνσταντίνος → Κωνσταντίνε, Αλέξανδρος → Αλέξανδρε |
265
+ | Other longer first names (3+ syllables) → **-ε** | Πρόδρομος → Πρόδρομε |
266
+ | Paroxytone surnames → **-ο** | Παπαδόπουλος → Παπαδόπουλο |
267
+ | Oxytone surnames → **-ε** | Ξινός → Ξινέ |
268
+ | Oxytone first names | Often unchanged (e.g. Νικολός) |
269
+
270
+ Rules are loaded from `names_klitiki.md` in Node.js when available, with the same logic as hard-coded fallbacks in `src/cases.js` and `src/constants.js`.
271
+
272
+ ### Accusative Case Conversion (Αιτιατική)
273
+
274
+ Use `convertToCase: 'accusative'` for **direct objects** (e.g. «Είδα τον …», «Καλώ την …»).
275
+
245
276
  ```javascript
246
277
  // Convert to accusative case (for direct objects)
247
278
  GreekNameCorrection('Γιώργος Παπαδόπουλος', {
@@ -249,6 +280,13 @@ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
249
280
  });
250
281
  // → "Γιώργο Παπαδόπουλο"
251
282
 
283
+ // All masculine -ος names → -ο in accusative (unlike vocative)
284
+ GreekNameCorrection('Πρόδρομος', { convertToCase: 'accusative' });
285
+ // → "Πρόδρομο"
286
+
287
+ GreekNameCorrection('Στέφανος', { convertToCase: 'accusative' });
288
+ // → "Στέφανο"
289
+
252
290
  // With preserveOriginal to get both forms
253
291
  GreekNameCorrection('Κώστας Παπαδάκης', {
254
292
  convertToCase: 'accusative',
@@ -266,6 +304,39 @@ GreekNameCorrection('Μαρία Κωνσταντίνου', {
266
304
  // → "Μαρία Κωνσταντίνου"
267
305
  ```
268
306
 
307
+ **Masculine accusative endings** (consistent; no stress-based split for -ος):
308
+
309
+ | Ending | Rule | Examples |
310
+ |--------|------|----------|
311
+ | **-ος** | Always **-ο** | Πρόδρομος → Πρόδρομο, Στέφανος → Στέφανο, Γιώργος → Γιώργο |
312
+ | **-ης** | **-η** | Δημήτρης → Δημήτρη, Γιάννης → Γιάννη |
313
+ | **-ας** | **-α** (or **-η** if name follows -ης pattern) | Κώστας → Κώστα, Θανάσης → Θανάση |
314
+ | **-ούς** | **-ού** | (rare in names) |
315
+
316
+ Rules are loaded from `names_aitiatiki.md` in Node.js when available.
317
+
318
+ ### Vocative vs Accusative (do not confuse)
319
+
320
+ For the same nominative name, **vocative** and **accusative** can differ when the vocative uses **-ε**:
321
+
322
+ ```javascript
323
+ const name = 'Πρόδρομος';
324
+
325
+ GreekNameCorrection(name, { convertToCase: 'accusative' }); // → "Πρόδρομο" (τον Πρόδρομο)
326
+ GreekNameCorrection(name, { convertToCase: 'vocative' }); // → "Πρόδρομε" (direct address)
327
+
328
+ GreekNameCorrection('Στέφανος', { convertToCase: 'accusative' }); // → "Στέφανο"
329
+ GreekNameCorrection('Στέφανος', { convertToCase: 'vocative' }); // → "Στέφανε"
330
+ ```
331
+
332
+ | Name (ονομαστική) | Accusative (αιτιατική) | Vocative (κλητική) | Typical use |
333
+ |-------------------|------------------------|-------------------|-------------|
334
+ | Πρόδρομος | Πρόδρομο | Πρόδρομε | τον Πρόδρομο / «Πρόδρομε!» |
335
+ | Στέφανος | Στέφανο | Στέφανε | τον Στέφανο / «Στέφανε!» |
336
+ | Γιώργος | Γιώργο | Γιώργο | τον Γιώργο / «Γιώργο!» |
337
+
338
+ Use **vocative** for calling or greeting someone; use **accusative** for object forms after verbs or with **τον/την**.
339
+
269
340
  ### Title Handling
270
341
  ```javascript
271
342
  GreekNameCorrection('δρ. γιώργος παπαδόπουλος', {
@@ -631,6 +702,295 @@ const { corrected, sortKey } = GreekNameCorrection(name, {
631
702
  // [corrected, sortKey]
632
703
  ```
633
704
 
705
+ ## Option Interactions and Processing Order
706
+
707
+ When using multiple options together, it's important to understand the processing order and how options interact with each other. This section explains the execution pipeline, option conflicts, and best practices.
708
+
709
+ ### Processing Order
710
+
711
+ The library processes names in a specific order to ensure correct results. Here's the exact sequence:
712
+
713
+ ```mermaid
714
+ flowchart TD
715
+ Start[Input Name] --> ExtractTitle[1. Extract Title<br/>handleTitles]
716
+ ExtractTitle --> Transliterate[2. Transliteration<br/>transliterate]
717
+ Transliterate --> RemoveSpaces[3. Remove Extra Spaces<br/>removeExtraSpaces]
718
+ RemoveSpaces --> Katharevousa[4. Katharevousa Conversion<br/>recognizeKatharevousa]
719
+ Katharevousa --> Suggest[5. Suggest Corrections<br/>suggestCorrections]
720
+ Suggest --> Normalize[6. Normalize Tonotics<br/>normalizeTonotics]
721
+ Normalize --> Diacritics[7. Handle Diacritics<br/>handleDiacritics]
722
+ Diacritics --> Capitalize[8. Capitalize & Split<br/>splitNames]
723
+ Capitalize --> AddAccents[9. Add Accents<br/>addAccents]
724
+ AddAccents --> Genitive[10. Convert to Genitive<br/>convertToGenitive<br/>stored separately]
725
+ Genitive --> DatabaseSafe[11. Database-Safe<br/>databaseSafe]
726
+ DatabaseSafe --> AddTitle[12. Add General Title<br/>addGeneralTitle]
727
+ AddTitle --> CaseConvert[13. Convert to Case<br/>convertToCase]
728
+ CaseConvert --> ReattachTitle[14. Re-attach Title]
729
+ ReattachTitle --> Metadata[15. Generate Metadata<br/>detectGender, detectDiminutive<br/>generateSortKey, statistics]
730
+ Metadata --> End[Output Result]
731
+ ```
732
+
733
+ #### Detailed Processing Steps
734
+
735
+ 1. **Title Extraction** (`handleTitles: true` by default)
736
+ - Extracts titles like "Δρ.", "Καθ.", "Κ." from the beginning of the name
737
+ - Title is stored separately and removed from processing
738
+
739
+ 2. **Transliteration** (`transliterate`)
740
+ - Converts between Greek, Greeklish, and Latin scripts
741
+ - Applied early to ensure subsequent steps work on the correct script
742
+
743
+ 3. **Space Normalization** (`removeExtraSpaces: true` by default)
744
+ - Removes extra whitespace and trims the name
745
+
746
+ 4. **Katharevousa Recognition** (`recognizeKatharevousa`)
747
+ - Converts archaic Greek forms to modern Greek
748
+
749
+ 5. **Suggestion Corrections** (`suggestCorrections`)
750
+ - Suggests corrections for common misspellings
751
+ - Applied before normalization to fix errors early
752
+
753
+ 6. **Tonotic Normalization** (`normalizeTonotics: true` by default)
754
+ - Normalizes Greek accent marks to standard forms
755
+
756
+ 7. **Diacritic Handling** (`handleDiacritics: true` by default)
757
+ - Properly handles Greek diacritics (diaeresis, etc.)
758
+
759
+ 8. **Capitalization & Splitting** (`splitNames: true` by default)
760
+ - Splits name into parts and capitalizes each part
761
+ - Handles particles (του/της/των) if `handleParticles: true`
762
+
763
+ 9. **Accent Addition** (`addAccents`)
764
+ - Adds accents to unaccented Greek names
765
+ - Uses comprehensive name dictionary with fallback rules
766
+
767
+ 10. **Genitive Conversion** (`convertToGenitive`)
768
+ - Converts to genitive case
769
+ - **Stored separately** in result object, doesn't modify main processed name
770
+
771
+ 11. **Database-Safe Conversion** (`databaseSafe`)
772
+ - Removes problematic characters for database storage
773
+
774
+ 12. **General Title Addition** (`addGeneralTitle`)
775
+ - Adds "Κ." (for men) or "Κα" (for women) if no title exists
776
+ - Only adds if no title was extracted in step 1
777
+
778
+ 13. **Case Conversion** (`convertToCase: 'vocative' | 'accusative'`)
779
+ - **Vocative (κλητική):** name-dependent **-ο** / **-ε** for masculine **-ος** (see [Vocative Case Conversion](#vocative-case-conversion-κλητική))
780
+ - **Accusative (αιτιατική):** masculine **-ος** always → **-ο**; **-ης** → **-η**; **-ας** → **-α** (see [Accusative Case Conversion](#accusative-case-conversion-αιτιατική))
781
+ - **Stored separately** in result object
782
+ - If `preserveOriginal: false`, returns case form directly
783
+
784
+ 14. **Title Re-attachment**
785
+ - Re-attaches extracted or added title to the processed name
786
+
787
+ 15. **Metadata Generation**
788
+ - `detectGender`: Detects gender from name endings
789
+ - `detectDiminutive`: Detects diminutive forms
790
+ - `generateSortKey`: Generates accent-free sort key
791
+ - `statistics`: Generates comprehensive name statistics
792
+
793
+ ### Option Conflicts and Incompatibilities
794
+
795
+ Some options don't work well together or have special behaviors:
796
+
797
+ #### Conflicting Combinations
798
+
799
+ | Combination | Issue | Solution |
800
+ |------------|-------|----------|
801
+ | `transliterate: 'greek-to-latin'` + `addAccents` | Accents can't be added to Latin text | Use `addAccents` only with Greek text |
802
+ | `transliterate: 'greek-to-latin'` + `convertToCase` | Case conversion only works on Greek text | Case conversion requires Greek input |
803
+ | `transliterate: 'greek-to-greeklish'` + `convertToCase` | Case conversion only works on Greek text | Case conversion requires Greek input |
804
+ | `databaseSafe: true` + `addAccents` | Database-safe may remove characters needed for accents | Apply `addAccents` before `databaseSafe` (automatic) |
805
+ | `convertToCase` + `preserveOriginal: false` | Returns case form string, not main corrected name | Use `preserveOriginal: true` to get both forms |
806
+
807
+ #### Special Behaviors
808
+
809
+ **Case Conversion Return Value**
810
+ - When `convertToCase` is set and `preserveOriginal: false`, the function returns the case form string directly (not the nominative form)
811
+ - When `preserveOriginal: true`, both the corrected nominative and case forms are included in the result object
812
+
813
+ ```javascript
814
+ // Returns case form directly
815
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
816
+ convertToCase: 'vocative'
817
+ });
818
+ // → "Γιώργο Παπαδόπουλο"
819
+
820
+ // Returns object with both forms
821
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
822
+ convertToCase: 'vocative',
823
+ preserveOriginal: true
824
+ });
825
+ // → {
826
+ // corrected: "Γιώργος Παπαδόπουλος",
827
+ // vocative: "Γιώργο Παπαδόπουλο",
828
+ // ...
829
+ // }
830
+ ```
831
+
832
+ **Genitive and Case Conversion Together**
833
+ - Both `convertToGenitive` and `convertToCase` can be used together
834
+ - Genitive form is stored in `result.genitive`
835
+ - Case form is stored in `result.vocative` or `result.accusative`
836
+ - The main `corrected` field contains the nominative form
837
+
838
+ **General Title Addition**
839
+ - `addGeneralTitle` only adds a title if no title was extracted in step 1
840
+ - If a title already exists, it won't add another one
841
+
842
+ **Accent Addition Limitations**
843
+ - `addAccents` works best on Greek text
844
+ - May not work correctly after transliteration to non-Greek scripts
845
+ - Uses dictionary lookup first, then falls back to heuristic rules
846
+
847
+ ### Best Practices
848
+
849
+ #### Recommended Combinations
850
+
851
+ **Basic Name Correction**
852
+ ```javascript
853
+ GreekNameCorrection('γιώργος παπαδόπουλος', {
854
+ addAccents: true,
855
+ normalizeTonotics: true,
856
+ handleDiacritics: true
857
+ });
858
+ // Safe and effective for most use cases
859
+ ```
860
+
861
+ **Full Name Processing with Metadata**
862
+ ```javascript
863
+ GreekNameCorrection('γιώργος παπαδόπουλος', {
864
+ preserveOriginal: true,
865
+ addAccents: true,
866
+ detectGender: true,
867
+ convertToGenitive: true,
868
+ generateSortKey: true
869
+ });
870
+ // Gets all information without conflicts
871
+ ```
872
+
873
+ **Case Conversion with Full Details**
874
+ ```javascript
875
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
876
+ preserveOriginal: true,
877
+ convertToCase: 'vocative',
878
+ convertToGenitive: true,
879
+ detectGender: true
880
+ });
881
+ // Returns object with nominative, vocative, and genitive forms
882
+ ```
883
+
884
+ **Transliteration Workflow**
885
+ ```javascript
886
+ // Step 1: Convert Greeklish to Greek
887
+ const greek = GreekNameCorrection('giorgos papadopoulos', {
888
+ transliterate: 'greeklish-to-greek',
889
+ addAccents: true
890
+ });
891
+
892
+ // Step 2: Process the Greek name
893
+ const processed = GreekNameCorrection(greek, {
894
+ convertToCase: 'vocative',
895
+ detectGender: true
896
+ });
897
+ // Separate transliteration from case conversion
898
+ ```
899
+
900
+ #### Problematic Combinations to Avoid
901
+
902
+ **Don't combine transliteration to non-Greek with case conversion:**
903
+ ```javascript
904
+ // ❌ Won't work - case conversion needs Greek text
905
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
906
+ transliterate: 'greek-to-latin',
907
+ convertToCase: 'vocative'
908
+ });
909
+ ```
910
+
911
+ **Don't use addAccents after transliteration to non-Greek:**
912
+ ```javascript
913
+ // ❌ Won't work - accents can't be added to Latin
914
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
915
+ transliterate: 'greek-to-latin',
916
+ addAccents: true
917
+ });
918
+ ```
919
+
920
+ **Do use preserveOriginal when you need multiple forms:**
921
+ ```javascript
922
+ // ✅ Correct - gets all forms
923
+ GreekNameCorrection('Γιώργος Παπαδόπουλος', {
924
+ preserveOriginal: true,
925
+ convertToGenitive: true,
926
+ convertToCase: 'vocative'
927
+ });
928
+ ```
929
+
930
+ ### Examples of Option Combinations
931
+
932
+ #### Example 1: Safe Combination - All Features
933
+ ```javascript
934
+ const result = GreekNameCorrection('dr giorgos tou papa', {
935
+ transliterate: 'greeklish-to-greek',
936
+ preserveOriginal: true,
937
+ handleTitles: true,
938
+ addGeneralTitle: true,
939
+ addAccents: true,
940
+ handleParticles: true,
941
+ suggestCorrections: true,
942
+ detectGender: true,
943
+ convertToGenitive: true,
944
+ convertToCase: 'vocative',
945
+ generateSortKey: true,
946
+ statistics: true,
947
+ detectDiminutive: true
948
+ });
949
+
950
+ // Result includes:
951
+ // - corrected: Main corrected name
952
+ // - genitive: Genitive form
953
+ // - vocative: Vocative form
954
+ // - gender: Detected gender
955
+ // - sortKey: Sort key
956
+ // - statistics: Name statistics
957
+ // - diminutive: Diminutive detection
958
+ // - title: Extracted title
959
+ ```
960
+
961
+ #### Example 2: Database Integration
962
+ ```javascript
963
+ const result = GreekNameCorrection('Γιώργος Παπαδόπουλος', {
964
+ preserveOriginal: true,
965
+ databaseSafe: true,
966
+ generateSortKey: true
967
+ });
968
+
969
+ // Safe for database storage
970
+ // result.corrected - safe for display
971
+ // result.sortKey - safe for sorting
972
+ ```
973
+
974
+ #### Example 3: Case Conversion Only
975
+ ```javascript
976
+ // Simple case conversion - returns string
977
+ const vocative = GreekNameCorrection('Γιώργος Παπαδόπουλος', {
978
+ convertToCase: 'vocative'
979
+ });
980
+ // → "Γιώργο Παπαδόπουλο"
981
+ ```
982
+
983
+ #### Example 4: Problematic Combination
984
+ ```javascript
985
+ // ⚠️ Warning: This won't work as expected
986
+ const result = GreekNameCorrection('Γιώργος Παπαδόπουλος', {
987
+ transliterate: 'greek-to-latin',
988
+ convertToCase: 'vocative', // Won't work on Latin text
989
+ addAccents: true // Won't work on Latin text
990
+ });
991
+ // Transliteration happens first, so case conversion and accent addition fail
992
+ ```
993
+
634
994
  ## Use Cases
635
995
 
636
996
  ### 1. Form Validation & Correction
@@ -685,18 +1045,20 @@ const recipient = GreekNameCorrection(name, {
685
1045
  console.log(`Προς: ${recipient.genitive}`);
686
1046
 
687
1047
  // Use vocative case for addressing someone
688
- const addressee = GreekNameCorrection('Γιώργος Παπαδόπουλος', {
1048
+ const addressee = GreekNameCorrection('Πρόδρομος', {
689
1049
  convertToCase: 'vocative'
690
1050
  });
1051
+ console.log(`Γεια σου, ${addressee}!`); // "Γεια σου, Πρόδρομε!"
691
1052
 
692
- console.log(`Αγαπητέ ${addressee},`); // "Αγαπητέ Γιώργο Παπαδόπουλο,"
693
-
694
- // Use accusative case for direct objects
695
- const object = GreekNameCorrection('Δημήτρης Νικολάου', {
1053
+ // Use accusative case for direct objects (with article τον/την)
1054
+ const object = GreekNameCorrection('Πρόδρομος', {
696
1055
  convertToCase: 'accusative'
697
1056
  });
1057
+ console.log(`Είδα τον ${object}.`); // "Είδα τον Πρόδρομο."
698
1058
 
699
- console.log(`Είδα τον ${object}`); // "Είδα τον Δημήτρη Νικολάου"
1059
+ // Same name, different case do not use vocative where accusative is required
1060
+ GreekNameCorrection('Στέφανος', { convertToCase: 'accusative' }); // → "Στέφανο" (τον Στέφανο)
1061
+ GreekNameCorrection('Στέφανος', { convertToCase: 'vocative' }); // → "Στέφανε" (Στέφανε!)
700
1062
  ```
701
1063
 
702
1064
  ### 5. Gender-Based Processing
@@ -719,7 +1081,29 @@ console.log(`${pronoun} ${person.corrected}`);
719
1081
 
720
1082
  ## Browser Support
721
1083
 
722
- While designed for Node.js, the library can be bundled for browser use with tools like Webpack or Browserify.
1084
+ The library is fully compatible with browser environments! It automatically detects the runtime environment and works seamlessly in both Node.js and browsers.
1085
+
1086
+ ### Features
1087
+
1088
+ - ✅ **Automatic Environment Detection** - No configuration needed
1089
+ - ✅ **Zero Browser Errors** - No `__dirname` or Node.js-specific globals required
1090
+ - ✅ **Full Feature Support** - All features work in browsers, including case conversion
1091
+ - ✅ **Smart Fallback** - Uses hard-coded rules in browsers, markdown files in Node.js when available
1092
+
1093
+ ### Usage in Browser
1094
+
1095
+ ```javascript
1096
+ // Works directly in browser (with bundler like Webpack, Browserify, or Vite)
1097
+ import GreekNameCorrection from 'greek-name-correction';
1098
+
1099
+ // All features work, including case conversion
1100
+ const result = GreekNameCorrection('Γιώργος Παπαδόπουλος', {
1101
+ convertToCase: 'vocative',
1102
+ detectGender: true
1103
+ });
1104
+ ```
1105
+
1106
+ The library automatically uses hard-coded rules for case conversion in browser environments, ensuring full functionality without file system access.
723
1107
 
724
1108
  ## TypeScript
725
1109
 
@@ -804,7 +1188,7 @@ The test suite covers:
804
1188
  - Array processing
805
1189
  - Object processing
806
1190
  - All transliteration modes
807
- - Case conversions (genitive, vocative, accusative)
1191
+ - Case conversions (genitive, vocative, accusative), including vocative vs accusative for names like Πρόδρομος
808
1192
  - Title handling
809
1193
  - Automatic general title addition
810
1194
  - Accent addition
@@ -815,7 +1199,18 @@ The test suite covers:
815
1199
 
816
1200
  ## Changelog
817
1201
 
818
- ### Version 2.2.1 (Current)
1202
+ ### Version 2.2.3 (Current)
1203
+ - 📖 **Case conversion documentation** - README documents vocative (κλητική) vs accusative (αιτιατική), including masculine **-ος** names where forms differ (e.g. Πρόδρομος → **Πρόδρομο** / **Πρόδρομε**; Στέφανος → **Στέφανο** / **Στέφανε**)
1204
+ - 🔧 **Vocative logic refactor** - Shared `resolveOsVocativeStyleEnding()` in `src/cases.js` for clearer **-ο** / **-ε** handling
1205
+ - ✅ **Accusative consistency** - Masculine **-ος** names always form accusative in **-ο**; vocative keeps separate name-dependent rules
1206
+ - ✅ **Tests** - Added coverage for Πρόδρομος accusative vs vocative forms
1207
+ - 📝 See [README_NOTES_2.2.3.md](./README_NOTES_2.2.3.md) for full release notes
1208
+
1209
+ ### Version 2.2.2
1210
+ - 🌐 **Browser compatibility** - Environment detection and fallback to hard-coded rules when `__dirname` / file system is unavailable
1211
+ - 🔧 **Package configuration** - Updated repository URL format in package.json
1212
+
1213
+ ### Version 2.2.1
819
1214
  - 🔧 **Enhanced Accent Addition** - Improved `addAccents` feature with comprehensive name dictionary support. Now uses actual Greek name dictionaries from `generate_greek_names.js` for accurate accent placement on common names. Includes CLI support with `-addAccents` flag.
820
1215
  - ✨ **Name Dictionary** - Built-in dictionary with 1,100+ Greek names (first names, surnames, compound surnames) for accurate accent placement
821
1216
  - 🐛 **CLI Fix** - Added missing `-addAccents` flag to command-line interface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "greek-name-correction",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "A zero-dependency Node.js library for correcting and formatting Greek names with transliteration, genitive conversion, and advanced features",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "license": "MIT",
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "https://github.com/sraftopo/greek-name-correction.git"
29
+ "url": "git+https://github.com/sraftopo/greek-name-correction.git"
30
30
  },
31
31
  "bugs": {
32
32
  "url": "https://github.com/sraftopo/greek-name-correction/issues"
@@ -36,7 +36,7 @@
36
36
  "node": ">=12.0.0"
37
37
  },
38
38
  "bin": {
39
- "greek-name-correction": "./bin/greek-name-correction.js"
39
+ "greek-name-correction": "bin/greek-name-correction.js"
40
40
  },
41
41
  "files": [
42
42
  "src/",
@@ -54,9 +54,15 @@
54
54
  "collectCoverageFrom": [
55
55
  "src/**/*.js"
56
56
  ],
57
+ "coverageDirectory": "coverage",
58
+ "coverageReporters": [
59
+ "text",
60
+ "lcov",
61
+ "json"
62
+ ],
57
63
  "coveragePathIgnorePatterns": [
58
- "/node_modules/",
59
- "/test/"
64
+ "node_modules",
65
+ "test"
60
66
  ],
61
67
  "coverageThreshold": {
62
68
  "global": {
@@ -67,4 +73,4 @@
67
73
  }
68
74
  }
69
75
  }
70
- }
76
+ }
package/src/cases.js CHANGED
@@ -163,6 +163,40 @@ function isDiminutivePattern(lowerPart) {
163
163
  );
164
164
  }
165
165
 
166
+ // Resolve -ος ending as "ο", "ε", or null (unchanged) using vocative-style rules
167
+ function resolveOsVocativeStyleEnding(part, index, totalParts, parsedRules) {
168
+ const lowerPart = part.toLowerCase();
169
+
170
+ if (isDiminutivePattern(lowerPart)) {
171
+ return "ο";
172
+ }
173
+
174
+ const firstNamesInO =
175
+ parsedRules?.osEndings?.firstNames?.paroxytone?.inO ||
176
+ vocativeInO_FirstNames;
177
+ const firstNamesInE =
178
+ parsedRules?.osEndings?.firstNames?.paroxytone?.inE ||
179
+ vocativeInE_FirstNames;
180
+ const firstNamesUnchanged =
181
+ parsedRules?.osEndings?.firstNames?.oxytone?.unchanged || oxytoneNames;
182
+
183
+ if (firstNamesInO.includes(lowerPart)) {
184
+ return "ο";
185
+ }
186
+ if (firstNamesInE.includes(lowerPart)) {
187
+ return "ε";
188
+ }
189
+ if (isLikelySurname(part, index, totalParts)) {
190
+ return isOxytoneSurname(part) ? "ε" : "ο";
191
+ }
192
+ if (firstNamesUnchanged.includes(lowerPart) || isOxytone(part)) {
193
+ return null;
194
+ }
195
+
196
+ const syllableCount = (part.match(/[αεηιουωάέήίόύώ]/gi) || []).length;
197
+ return syllableCount <= 2 ? "ο" : "ε";
198
+ }
199
+
166
200
  // Convert to vocative case
167
201
  // Converts all name parts (first name and last name), but skips particles
168
202
  function convertToVocative(name, config) {
@@ -196,55 +230,14 @@ function convertToVocative(name, config) {
196
230
 
197
231
  // Handle masculine names ending in -ος
198
232
  if (lowerPart.endsWith("ος")) {
199
- // Check if it's a diminutive pattern first
200
- if (isDiminutivePattern(lowerPart)) {
201
- // Diminutives: -ος → -ο
202
- vocative = part.slice(0, -2) + "ο";
203
- }
204
- // Use parsed rules if available, otherwise use hard-coded lists
205
- else {
206
- // Get name lists from parsed rules or fallback to hard-coded
207
- const firstNamesInO = parsedRules?.osEndings?.firstNames?.paroxytone?.inO || vocativeInO_FirstNames;
208
- const firstNamesInE = parsedRules?.osEndings?.firstNames?.paroxytone?.inE || vocativeInE_FirstNames;
209
- const firstNamesUnchanged = parsedRules?.osEndings?.firstNames?.oxytone?.unchanged || oxytoneNames;
210
-
211
- // Check explicit list of first names that form vocative in -ο
212
- if (firstNamesInO.includes(lowerPart)) {
213
- vocative = part.slice(0, -2) + "ο";
214
- }
215
- // Check explicit list of first names that form vocative in -ε
216
- else if (firstNamesInE.includes(lowerPart)) {
217
- vocative = part.slice(0, -2) + "ε";
218
- }
219
- // Check if it's likely a surname (by position or explicit list)
220
- else if (isLikelySurname(part, index, totalParts)) {
221
- // For surnames, check if oxytone
222
- if (isOxytoneSurname(part)) {
223
- // Oxytone surname: -ος → -ε
224
- vocative = part.slice(0, -2) + "ε";
225
- } else {
226
- // Paroxytone surname: -ος → -ο
227
- vocative = part.slice(0, -2) + "ο";
228
- }
229
- }
230
- // It's a first name (not in explicit lists) - default behavior
231
- else {
232
- if (firstNamesUnchanged.includes(lowerPart) || isOxytone(part)) {
233
- // Oxytone first name: remains unchanged
234
- vocative = part;
235
- } else {
236
- // Paroxytone first name: default to -ο for common short names, -ε for longer names
237
- // Default to -ο for disyllabic names, -ε for longer
238
- const syllableCount = (part.match(/[αεηιουωάέήίόύώ]/gi) || []).length;
239
- if (syllableCount <= 2) {
240
- // Disyllabic: -ος → -ο
241
- vocative = part.slice(0, -2) + "ο";
242
- } else {
243
- // Longer names: -ος → -ε
244
- vocative = part.slice(0, -2) + "ε";
245
- }
246
- }
247
- }
233
+ const ending = resolveOsVocativeStyleEnding(
234
+ part,
235
+ index,
236
+ totalParts,
237
+ parsedRules
238
+ );
239
+ if (ending) {
240
+ vocative = part.slice(0, -2) + ending;
248
241
  }
249
242
  }
250
243
  // Handle masculine names ending in -ης
@@ -290,7 +283,7 @@ function convertToVocative(name, config) {
290
283
  function convertToAccusative(name, config) {
291
284
  // Try to load parsed rules from markdown, fallback to hard-coded rules
292
285
  const parsedRules = getAccusativeRules();
293
-
286
+
294
287
  const parts = name.split(/\s+/);
295
288
  const processedParts = parts.map((part) => {
296
289
  const lowerPart = part.toLowerCase();
@@ -1,8 +1,31 @@
1
1
  // rulesParser.js
2
2
  "use strict";
3
3
 
4
- const fs = require("fs");
5
- const path = require("path");
4
+ // Browser environment detection
5
+ // Check if Node.js globals are available
6
+ function isNodeEnvironment() {
7
+ return (
8
+ typeof __dirname !== "undefined" &&
9
+ typeof require !== "undefined" &&
10
+ typeof module !== "undefined"
11
+ );
12
+ }
13
+
14
+ // Safely require Node.js modules only in Node.js environment
15
+ let fs, path;
16
+ if (isNodeEnvironment()) {
17
+ try {
18
+ fs = require("fs");
19
+ path = require("path");
20
+ } catch (e) {
21
+ // If require fails, we're likely in a browser environment
22
+ fs = null;
23
+ path = null;
24
+ }
25
+ } else {
26
+ fs = null;
27
+ path = null;
28
+ }
6
29
 
7
30
  /**
8
31
  * Parse markdown file and extract Greek name case conversion rules
@@ -10,6 +33,11 @@ const path = require("path");
10
33
  * @returns {Object} Parsed rules structure
11
34
  */
12
35
  function parseMarkdownRules(filePath) {
36
+ // In browser environments, fs is not available
37
+ if (!fs || !path) {
38
+ return null;
39
+ }
40
+
13
41
  try {
14
42
  const content = fs.readFileSync(filePath, "utf8");
15
43
  return parseMarkdownContent(content);
@@ -260,6 +288,12 @@ function extractEnding(name) {
260
288
  * Parse vocative rules from names_klitiki.md
261
289
  */
262
290
  function parseVocativeRules() {
291
+ // In browser environments, skip file reading and return null
292
+ // This will trigger fallback to hard-coded rules in cases.js
293
+ if (!isNodeEnvironment() || !path || typeof __dirname === "undefined") {
294
+ return null;
295
+ }
296
+
263
297
  const filePath = path.join(__dirname, "..", "names_klitiki.md");
264
298
  const rules = parseMarkdownRules(filePath);
265
299
 
@@ -448,6 +482,12 @@ function extractNameListsFromExamples(structuredRules, examples) {
448
482
  * Parse accusative rules from names_aitiatiki.md
449
483
  */
450
484
  function parseAccusativeRules() {
485
+ // In browser environments, skip file reading and return null
486
+ // This will trigger fallback to hard-coded rules in cases.js
487
+ if (!isNodeEnvironment() || !path || typeof __dirname === "undefined") {
488
+ return null;
489
+ }
490
+
451
491
  const filePath = path.join(__dirname, "..", "names_aitiatiki.md");
452
492
  const rules = parseMarkdownRules(filePath);
453
493