Skip to content

feat: Add Urdu (ur) language localization#1683

Open
iamahsanmehmood wants to merge 5 commits intoHumanizr:mainfrom
iamahsanmehmood:feat/add-urdu-localization
Open

feat: Add Urdu (ur) language localization#1683
iamahsanmehmood wants to merge 5 commits intoHumanizr:mainfrom
iamahsanmehmood:feat/add-urdu-localization

Conversation

@iamahsanmehmood
Copy link
Copy Markdown

Summary

Adds complete Urdu (ur) language support to Humanizer. Urdu is spoken by 230+ million people worldwide (primarily Pakistan and India) and was not previously supported.

What's included

Source files (4):

File Description
Resources.ur.resx 45 Urdu translations for DateTime.Humanize() and TimeSpan.Humanize()
UrduNumberToWordsConverter.cs Full ToWords() and ToOrdinalWords() implementation with South Asian numbering
NumberToWordsConverterRegistry.cs Registered ur locale
FormatterRegistry.cs Registered ur locale with DefaultFormatter

Test files (3):

File Coverage
DateHumanizeTests.cs 10 test methods — all time units (past/future)
TimeSpanHumanizeTests.cs 9 test methods + NoTime + NoTimeToWords
NumberToWordsTests.cs 10 test methods — 0–99, hundreds, thousands, lakhs, crores, arabs, negatives, ordinals

Design decisions

  1. South Asian numbering system — Urdu uses لاکھ (lakh = 100,000), کروڑ (crore = 10,000,000), ارب (arab = 1,000,000,000) instead of Western million/billion. This is how numbers are actually spoken in Pakistan and India.

  2. Unique words for 1–99 — Unlike most languages that have patterns (e.g., twenty-one, twenty-two), Urdu has a completely unique word for every number from 1 to 99. All 100 words are included.

  3. GenderlessNumberToWordsConverter — Urdu cardinal numbers don't change based on grammatical gender (unlike Arabic), so the simpler base class is used, following the same pattern as Farsi.

  4. DefaultFormatter — Urdu pluralization doesn't require special dual/plural forms like Arabic, so the default formatter is sufficient.

Examples

// DateTime.Humanize()
"2 دن پہلے"         → 2 days ago
"ایک گھنٹہ پہلے"    → an hour ago
"کل"                → yesterday / tomorrow
"ابھی"              → now

// TimeSpan.Humanize()
"ایک ہفتہ"          → one week
"3 ماہ"             → 3 months

// ToWords()
1234.ToWords()      → "ایک ہزار دو سو چونتیس"
100000.ToWords()    → "ایک لاکھ"
10000000.ToWords()  → "ایک کروڑ"

// ToOrdinalWords()
1.ToOrdinalWords()  → "پہلا"
3.ToOrdinalWords()  → "تیسرا"

PR Checklist

  • Implementation is clean
  • Code adheres to the existing coding standards; e.g. no curlies for one-line blocks, no redundant empty lines between methods or code blocks, spaces rather than tabs, etc.
  • No Code Analysis warnings
  • There is proper unit test coverage
  • If the code is copied from StackOverflow (or a blog or OSS) full disclosure is included — N/A, original work
  • There are very few or no comments
  • Xml documentation is added/updated for the addition/change
  • Your PR is (re)based on top of the latest commits from the main branch
  • Link to the issue(s) you're fixing — No existing issue; this PR adds new localization as encouraged in CONTRIBUTING.md
  • Readme is updated — Happy to add Urdu to the supported languages list if maintainers approve
  • Run either build.cmd or build.ps1Requires .NET 10 preview SDK; CI pipeline should validate

- Resources.ur.resx: 45 Urdu translations for DateHumanize and TimeSpanHumanize

- UrduNumberToWordsConverter: South Asian numbering (lakh/crore/arab)

- Unique Urdu words for numbers 1-99 with ordinal support

- Registered in NumberToWordsConverterRegistry and FormatterRegistry
- DateHumanizeTests: 10 test methods covering all time units (past/future)

- TimeSpanHumanizeTests: 9 test methods + NoTime + NoTimeToWords

- NumberToWordsTests: 10 test methods covering 0-99, hundreds, thousands, lakhs, crores, arabs, negatives, ordinals
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db30674307

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


if (number < 0)
{
return $"منفی {Convert(-number)}";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard long.MinValue before negating number

Negating a negative long here is unsafe for long.MinValue: in unchecked arithmetic -long.MinValue stays long.MinValue, so Convert(-number) recurses indefinitely and eventually stack-overflows. Any call like long.MinValue.ToWords() in Urdu will hard-crash instead of returning text.

Useful? React with 👍 / 👎.

- P1: Guard long.MinValue before negation to prevent infinite recursion

- P2: Use recursive Convert() instead of ConvertUpTo99() for group values, preventing IndexOutOfRangeException on very large numbers
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial Urdu (ur) localisation support to Humanizer, including resource strings for date/timespan humanization and an Urdu number-to-words converter, with corresponding localisation tests.

Changes:

  • Added Urdu localisation resources for DateHumanize and TimeSpanHumanize.
  • Implemented and registered an Urdu NumberToWords converter (ur) and enabled the default formatter for ur.
  • Added Urdu localisation test coverage for dates, timespans, and number/ordinal-to-words.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Humanizer.Tests/Localisation/ur/TimeSpanHumanizeTests.cs Adds Urdu assertions for TimeSpan.Humanize output.
tests/Humanizer.Tests/Localisation/ur/NumberToWordsTests.cs Adds Urdu assertions for cardinal and ordinal number-to-words.
tests/Humanizer.Tests/Localisation/ur/DateHumanizeTests.cs Adds Urdu assertions for DateHumanize past/future phrasing.
src/Humanizer/Properties/Resources.ur.resx Introduces Urdu resource strings for date/timespan humanization.
src/Humanizer/Localisation/NumberToWords/UrduNumberToWordsConverter.cs Adds Urdu number-to-words implementation (cardinal + ordinal).
src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs Registers the Urdu number-to-words converter for locale ur.
src/Humanizer/Configuration/FormatterRegistry.cs Enables default formatter registration for locale ur.

Comment on lines +76 to +85
if (number >= 100)
{
parts.Add(HundredsMap[number / 100]);
number %= 100;
}

if (number > 0)
{
parts.Add(UnitsMap[number]);
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won’t compile as written: array indexes must be int, but number / 100 and number are long here (e.g., HundredsMap[number / 100], UnitsMap[number]). Consider converting the remainder to an int after you’ve bounded it (e.g., after number %= 100) or explicitly casting to int where safe, so the converter builds on all target frameworks.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +107
static string ConvertUpTo99(long number)
{
if (number < 100)
{
return UnitsMap[number];
}

var result = HundredsMap[number / 100];
number %= 100;

if (number > 0)
{
result += " " + UnitsMap[number];
}

return result;
}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConvertUpTo99 is currently unused within this type. If it’s not intentionally kept for future work, consider removing it to reduce dead code and maintenance surface (or refactor Convert to use it if that was the intent).

Suggested change
static string ConvertUpTo99(long number)
{
if (number < 100)
{
return UnitsMap[number];
}
var result = HundredsMap[number / 100];
number %= 100;
if (number > 0)
{
result += " " + UnitsMap[number];
}
return result;
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@clairernovotny clairernovotny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the potential unchecked arithmetic overflow

…ove dead code

- Cast number to int for HundredsMap and UnitsMap array indexing (safe: value bounded to 0-99/0-9)

- Remove unused ConvertUpTo99 method (replaced by recursive Convert in prior commit)
@iamahsanmehmood
Copy link
Copy Markdown
Author

Hi @clairernovotny 👋

I've addressed all the review feedback:

  • long.MinValue guard — Added early return with hardcoded Urdu string to prevent infinite recursion from negation overflow
  • (int) casts — Added explicit casts for HundredsMap[(int)(number / 100)] and UnitsMap[(int)number] (safe: values bounded to 0–9 and 0–99 after group extraction)
  • Removed dead code — Deleted unused ConvertUpTo99 method (replaced by recursive Convert())
  • All 6 CI checks passing

Please let me know if anything else is needed — happy to make further adjustments!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants