diff --git a/num2words/lang_HR.py b/num2words/lang_HR.py index 380c12c1..2401c89d 100644 --- a/num2words/lang_HR.py +++ b/num2words/lang_HR.py @@ -134,7 +134,97 @@ def pluralize(self, number, forms): return forms[form] def to_ordinal(self, number): - raise NotImplementedError() + """Masculine nominative singular ordinal (the default ordinal form). + + Croatian ordinals decline by gender, case, and number — this method + returns the unmarked masculine-nominative-singular form (prvi, drugi, + peti, sedamnaesti, dvadeseti, stoti, tisućiti, ...). Compound numbers + replace only the last cardinal word with its ordinal counterpart. + """ + if int(number) != number: + raise ValueError("ordinals only defined for integers") + n = int(number) + if n in self._ORDINAL_EXACT: + return self._ORDINAL_EXACT[n] + cardinal_words = self._int2word(n).split() + last = cardinal_words[-1] + if last not in self._ORDINAL_LAST_MASC: + raise NotImplementedError( + "Croatian ordinal not defined for last word %r in %r" % ( + last, " ".join(cardinal_words) + ) + ) + cardinal_words[-1] = self._ORDINAL_LAST_MASC[last] + return " ".join(cardinal_words) + + def to_year(self, value, **kwargs): + """Year form used before 'godine' — feminine-genitive declension. + + Croatian convention reads "1986. godine" as + "tisuću devetsto osamdeset šeste godine" (last word = feminine + genitive ordinal). The leading "jedna tisuća" of cardinal years + 1000-1999 collapses to "tisuću". + """ + if int(value) != value: + raise ValueError("years only defined for integers") + n = int(value) + words = self._int2word(n).split() + # Collapse "jedna tisuća ..." → "tisuću ..." for years 1000-1999 + if len(words) >= 2 and words[0] == "jedna" and words[1] == "tisuća": + words = ["tisuću"] + words[2:] + last = words[-1] + if last in self._YEAR_LAST_FEM_GEN: + words[-1] = self._YEAR_LAST_FEM_GEN[last] + return " ".join(words) + + # Cardinal-last-word → masculine-nominative ordinal + _ORDINAL_LAST_MASC = { + "jedan": "prvi", "dva": "drugi", "tri": "treći", "četiri": "četvrti", + "pet": "peti", "šest": "šesti", "sedam": "sedmi", "osam": "osmi", + "devet": "deveti", + "deset": "deseti", + "jedanaest": "jedanaesti", "dvanaest": "dvanaesti", + "trinaest": "trinaesti", "četrnaest": "četrnaesti", + "petnaest": "petnaesti", "šesnaest": "šesnaesti", + "sedamnaest": "sedamnaesti", "osamnaest": "osamnaesti", + "devetnaest": "devetnaesti", + "dvadeset": "dvadeseti", "trideset": "trideseti", + "četrdeset": "četrdeseti", "pedeset": "pedeseti", + "šezdeset": "šezdeseti", "sedamdeset": "sedamdeseti", + "osamdeset": "osamdeseti", "devedeset": "devedeseti", + "sto": "stoti", "dvjesto": "dvjestoti", "tristo": "tristoti", + "četiristo": "četiristoti", "petsto": "petstoti", "šesto": "šestoti", + "sedamsto": "sedamstoti", "osamsto": "osamstoti", + "devetsto": "devetstoti", + } + + # Cardinal-last-word → feminine-genitive ordinal (year form, before "godine") + _YEAR_LAST_FEM_GEN = { + "jedan": "prve", "dva": "druge", "tri": "treće", "četiri": "četvrte", + "pet": "pete", "šest": "šeste", "sedam": "sedme", "osam": "osme", + "devet": "devete", + "deset": "desete", + "jedanaest": "jedanaeste", "dvanaest": "dvanaeste", + "trinaest": "trinaeste", "četrnaest": "četrnaeste", + "petnaest": "petnaeste", "šesnaest": "šesnaeste", + "sedamnaest": "sedamnaeste", "osamnaest": "osamnaeste", + "devetnaest": "devetnaeste", + "dvadeset": "dvadesete", "trideset": "tridesete", + "četrdeset": "četrdesete", "pedeset": "pedesete", + "šezdeset": "šezdesete", "sedamdeset": "sedamdesete", + "osamdeset": "osamdesete", "devedeset": "devedesete", + "sto": "stote", "dvjesto": "dvjestote", "tristo": "tristote", + "četiristo": "četiristote", "petsto": "petstote", "šesto": "šestote", + "sedamsto": "sedamstote", "osamsto": "osamstote", + "devetsto": "devetstote", + } + + # Whole-number ordinals where last-word substitution doesn't apply + _ORDINAL_EXACT = { + 1000: "tisućiti", + 1_000_000: "milijunti", + 1_000_000_000: "milijarditi", + } def _cents_verbose(self, number, currency): return self._int2word( diff --git a/tests/test_hr.py b/tests/test_hr.py index d3f6567e..5f089c86 100644 --- a/tests/test_hr.py +++ b/tests/test_hr.py @@ -91,8 +91,81 @@ def test_floating_point(self): ) def test_to_ordinal(self): - with self.assertRaises(NotImplementedError): - num2words(1, lang='hr', to='ordinal') + # ones — irregular forms + self.assertEqual("prvi", num2words(1, lang='hr', to='ordinal')) + self.assertEqual("drugi", num2words(2, lang='hr', to='ordinal')) + self.assertEqual("treći", num2words(3, lang='hr', to='ordinal')) + self.assertEqual("četvrti", num2words(4, lang='hr', to='ordinal')) + # ones — regular + self.assertEqual("peti", num2words(5, lang='hr', to='ordinal')) + self.assertEqual("šesti", num2words(6, lang='hr', to='ordinal')) + self.assertEqual("sedmi", num2words(7, lang='hr', to='ordinal')) + self.assertEqual("osmi", num2words(8, lang='hr', to='ordinal')) + self.assertEqual("deveti", num2words(9, lang='hr', to='ordinal')) + # tens + self.assertEqual("deseti", num2words(10, lang='hr', to='ordinal')) + self.assertEqual( + "sedamnaesti", num2words(17, lang='hr', to='ordinal') + ) + self.assertEqual( + "dvadeseti", num2words(20, lang='hr', to='ordinal') + ) + # compound — only last word becomes ordinal + self.assertEqual( + "dvadeset prvi", num2words(21, lang='hr', to='ordinal') + ) + self.assertEqual( + "trideset peti", num2words(35, lang='hr', to='ordinal') + ) + # hundreds + self.assertEqual("stoti", num2words(100, lang='hr', to='ordinal')) + self.assertEqual( + "sto prvi", num2words(101, lang='hr', to='ordinal') + ) + self.assertEqual( + "sto dvadeset peti", num2words(125, lang='hr', to='ordinal') + ) + # thousand — exact 10^k uses dedicated form + self.assertEqual( + "tisućiti", num2words(1000, lang='hr', to='ordinal') + ) + self.assertEqual( + "milijunti", num2words(1_000_000, lang='hr', to='ordinal') + ) + # year-shaped numbers as masculine ordinals + self.assertEqual( + "jedna tisuća devetsto osamdeset šesti", + num2words(1986, lang='hr', to='ordinal') + ) + self.assertEqual( + "dvije tisuće dvadeset četvrti", + num2words(2024, lang='hr', to='ordinal') + ) + + def test_to_year(self): + # 1000-1999 collapse "jedna tisuća" → "tisuću" and apply fem-gen ending + self.assertEqual( + "tisuću devetsto osamdeset šeste", + num2words(1986, lang='hr', to='year') + ) + self.assertEqual( + "tisuću devetsto četrdeset osme", + num2words(1948, lang='hr', to='year') + ) + # 2000+ keeps "dvije tisuće ..." prefix; last word still feminine genitive + self.assertEqual( + "dvije tisuće dvadeset četvrte", + num2words(2024, lang='hr', to='year') + ) + self.assertEqual( + "dvije tisuće trinaeste", + num2words(2013, lang='hr', to='year') + ) + # Round years + self.assertEqual( + "dvije tisuće", + num2words(2000, lang='hr', to='year') + ) def test_to_currency(self): self.assertEqual(