Skip to content

Locale support: abstract Locale, En, instance API, extension methods (v2.1 — §5 + §5a)#22

Open
mmucklo wants to merge 2 commits intomasterfrom
locale-support-v2.1
Open

Locale support: abstract Locale, En, instance API, extension methods (v2.1 — §5 + §5a)#22
mmucklo wants to merge 2 commits intomasterfrom
locale-support-v2.1

Conversation

@mmucklo
Copy link
Copy Markdown
Owner

@mmucklo mmucklo commented Apr 16, 2026

Implements ROADMAP items §5 (extensibility) and §5a (locale-based inflections) per the design locked in #20.

New files

  • `src/Locale/Locale.php` — abstract base class. Holds rule tables as `protected` instance arrays, inflection engine (`pluralize`/`singularize`), extension methods (`addIrregular`/`addUncountable`/`addPluralRule`/`addSingularRule`), per-instance caching with cache invalidation on mutation.
  • `src/Locale/En.php` — English locale. Seeds rule tables from `protected const` class constants via constructor. Identical rule set to v2.0 (enriching from `mmucklo/inflections` deferred to a follow-up).

Changed files

  • `src/Inflect.php` — rewritten as a facade. No breaking changes to the static API.

Static API (back-compat)

```php
Inflect::pluralize('cat'); // 'cats' — unchanged
Inflect::singularize('cats'); // 'cat' — unchanged
Inflect::pluralizeIf(3, 'person'); // '3 people' — unchanged
```
Internally delegates to a lazily-initialized shared `En` instance. New proxy extension methods:
```php
Inflect::addIrregular('dwarf', 'dwarves');
Inflect::addUncountable('moose');
Inflect::addPluralRule('/^(platypus)$/i', '$1es');
Inflect::addSingularRule('/(platypus)es$/i', '$1');
```

Instance API (new)

```php
$en = new Inflect(); // default 'en'
$en = new Inflect('en'); // explicit by name
$en = new Inflect(new En()); // explicit by object

$en->plural('cat'); // 'cats'
$en->singular('cats'); // 'cat'
$en->pluralIf(3, 'person'); // '3 people'
$en->getLocale()->addIrregular('formula', 'formulae');
```

Instances are isolated — extending one instance's rules doesn't affect another.

Locale registry

```php
Inflect::registerLocale('fr', Fr::class); // lazy: class instantiated on first use
$fr = new Inflect('fr');
```
Unknown locale throws `InvalidArgumentException`. Ships with `'en'` pre-registered.

Test plan

  • All 117 existing tests still pass (back-compat verified).
  • 12 new tests covering: instance API (plural, singular, pluralIf, construction from object, instance isolation), extension methods (addIrregular, addUncountable, addPluralRule, addSingularRule, cache invalidation), locale registration (register + resolve, unknown-locale exception), static extension proxy.
  • 129 tests / 141 assertions pass on PHP 8.1 and 8.3.
  • PHPStan level 8: no errors.
  • PHP-CS-Fixer: clean.
  • CI green on 8.1–8.4.

Follow-up (not in this PR)

  • Enrich `En` rule set from `mmucklo/inflections` (richer irregulars/uncountables).
  • Add at least one non-English locale (v2.2).
  • Update README and CHANGELOG.md once merged.

🤖 Generated with Claude Code

…n methods

Roadmap items §5 + §5a (v2.1).

Architecture:
- Inflect\Locale\Locale: abstract class holding rule tables as
  protected instance state, with a shared regex-rule inflection engine
  (pluralize, singularize) and extension methods (addIrregular,
  addUncountable, addPluralRule, addSingularRule). Per-instance
  memoization caches that clear on rule mutation.
- Inflect\Locale\En: English locale seeding rule tables from
  protected const class constants via the constructor.
- Inflect\Inflect: facade with both static and instance APIs.

Static API (back-compat — zero signature changes):
  Inflect::pluralize(), singularize(), pluralizeIf() delegate to a
  lazily-initialized shared En instance. Proxy extension methods
  (addIrregular, addUncountable, addPluralRule, addSingularRule)
  mutate that shared instance.

Instance API (new):
  new Inflect('en') or new Inflect(new En()) — per-instance locale,
  isolated cache and rules. Methods: plural(), singular(), pluralIf(),
  getLocale().

Locale registry:
  Inflect::registerLocale('name', Class::class|$instance) — lazy
  resolution via class-string. Ships with 'en' pre-registered.
  Unknown locale throws InvalidArgumentException.

Tests:
  12 new tests covering instance API (plural, singular, pluralIf,
  construction from Locale object, instance isolation), extension
  methods (addIrregular, addUncountable, addPluralRule,
  addSingularRule, cache invalidation), locale registration (register
  + resolve, unknown-locale exception), and the static extension proxy.

  129 tests / 141 assertions pass on PHP 8.1 and 8.3.
  PHPStan level 8: no errors. PHP-CS-Fixer: clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 93.45794% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.63%. Comparing base (82e3e89) to head (49a165e).

Files with missing lines Patch % Lines
src/Inflect.php 84.61% 6 Missing ⚠️
src/Locale/Locale.php 98.41% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #22      +/-   ##
============================================
- Coverage     98.07%   93.63%   -4.45%     
- Complexity       27       51      +24     
============================================
  Files             1        3       +2     
  Lines            52      110      +58     
============================================
+ Hits             51      103      +52     
- Misses            1        7       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…erage)

- Add tests for static proxy methods: addUncountable, addPluralRule,
  addSingularRule (lines 54, 59, 64 in Inflect.php were uncovered).
- Test the no-rule-match fallback in Locale::pluralize (line 62) using
  an anonymous empty Locale subclass — En's catch-all '/$/' => 's'
  makes this path unreachable with real English rules.

133 tests / 145 assertions. 100% line, method, and class coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant