You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Location data management: countries, states, cities, atolls and islands, with seedable data for every country and every Maldivian atoll and island, plus views and controllers.
Built to the next-gen general rules: CI with test and coverage badges, full docs for people and agents, Laravel Boost guideline and skill, Javaabu codestyle, PHP 8.4 / Laravel 12+ (Laravel packages) or TypeScript (JS packages), Dhivehi and RTL, Blade and Inertia React for views, overridable controllers and policies with javaabu/permissions.
Country, State, City, Atoll, Island. All are config-swappable, with a table prefix option.
Names:name (English) and dhivehi_name, plus localizedName() for the current locale. The column isn't called name_dv because that's translatable's suffix convention and would collide (Optional javaabu/translatable support #10).
Lookup endpoint:Route::locationOptions() adds a read-only JSON endpoint per type. It follows the forms select2 contract: filter[search], filter[country], filter[atoll], per_page=-1, fields.
Blade:<x-locations::country-select>, atoll-select, island-select, state-select, city-select, and a cascading <x-locations::address-selects>. They wrap <x-forms::select2> with child and filter-field.
React:CountrySelect, AtollSelect, IslandSelect (…) on @javaabu/formsSelect2, exported as @javaabu/locations through manifest.
Stencil:CountrySelect::make() and friends, as custom stencil fields.
Routes:Route::locations([...]) registers CRUD for countries, atolls, islands, states and cities. It takes guard, prefix, name, middleware, only and controllers options.
Controllers: one overridable controller per resource, on a shared base. Controllers are overridable from config.
Forms: defined once as stencil form classes, then rendered by Blade (javaabu/forms) or Inertia React (locations::Countries/Index …). The stack is chosen with config('locations.views').
Permissions:view_/edit_/delete_{countries,atolls,islands,states,cities}, registered through javaabu/permissions, with ResourcePolicy subclasses overridable from config or Gate::policy().
The JSON storage contract applies. Package models ship translatable subclasses, chosen when translatable is enabled. Seeding writes the Dhivehi names into the translations. Tested with and without it, plus a CI leg without it.
States and cities: they get models, pages and inputs. Only ISO 3166-2 top-level subdivisions are seeded as states (optional seeder, not for MV, whose subdivisions are the atolls). Cities are left for apps.
iso-codes licensing: its translations are LGPL-2.1+. The derived file keeps attribution, and the docs flag it for the user to confirm.
javaabu/locations v1.0 is on main. CI is green on the full matrix:
PHP 8.4/8.5 × Laravel 12/13 × lowest/stable
Pint
a leg without translatable and Inertia
a production-dependency check (no javaabu/helpers or javaabu/settings ^1)
a seed-data job that rebuilds the data from its sources and validates it
the JS job: lint, format, typecheck, Vitest with coverage
There are 105 PHPUnit tests and 19 Vitest tests. Every next-gen issue is closed except #11 (needs a human).
What was built
Models.Country, State, City, Atoll, Island, configurable through config('locations.models') and config('locations.tables').
Names: English name and dhivehi_name, with localizedName(). Islands also have formattedName() (ހއ. ދިއްދޫ).
Islands: an IslandCategories enum, plus scopes (ofAtoll('HA'), inhabited(), search(), ordered()), factories, and restricted deletes.
Seed data. Plain JSON in database/data, built by bin/build-data.php from pinned source snapshots and checked by bin/validate-data.php. The sources are cited in docs/advanced-usage/data-sources.md.
Countries: all 249 from ISO 3166-1 via Debian iso-codes v4.20.1. Dhivehi names come from iso-codes' translations for 191 of them. Calling codes come from libphonenumber v9.0.42.
Atolls: 20 administrative atolls and Malé City, with letter codes, Thaana codes and ISO 3166-2:MV codes. Official Dhivehi names come from the LGA's council names.
Islands: all 1,560 features of the MLSA/Geomatics OneMap island register (1,131 named), with feature codes, Dhivehi names, categories, coordinates and areas. The register matches the live 2024 layer as of today, and every LGA island council matches a residential island.
States: 3,569 top-level ISO 3166-2 subdivisions (optional).
Seeding:php artisan locations:seed and LocationsSeeder only add what's missing unless you pass --update.
Selects. Country, state, city, atoll and island selects, cascading through javaabu/forms' own select2 cascade from a read-only endpoint (Route::locationOptions()).
Admin pages.Route::locations() registers index, show, create, edit and delete for every type, in Blade or Inertia React (locations::Resources/*), and returns JSON on request.
Forms are stencil forms.
Each type has its own overridable controller.
Permissions are view_, edit_ and delete_{countries,states,cities,atolls,islands} through javaabu/permissions, with overridable policies.
Pages work with any guard; tests cover two.
Optional javaabu/translatable. Translatable model variants are used when it's enabled. Dhivehi names are seeded as translations, admin forms get an input per language, and dhivehi_name is kept in step.
Dhivehi and RTL. Strings in en and dv, permission descriptions in lang/dv.json, Thaana inputs, logical CSS only, and Thaana in the tests.
Docs. Docusaurus docs, llms.txt, AGENTS.md/CLAUDE.md, and the Laravel Boost guideline and javaabu-locations skill.
Decisions worth knowing
The Dhivehi column is dhivehi_name, not name_dv.name_dv would collide with translatable's {field}_{locale} attributes.
Atolls and islands have no country column. Malé City is the atoll MLE. An atoll select that depends on a country lists the atolls only for the Maldives, so forms' fallback can show a city field for foreign addresses.
Island names are spelled as the register spells them (Vilin'gili, Kurin'bi). The LGA's spellings are listed for review, not applied.
One set of generic admin pages serves all five types, driven by the controllers, instead of a page per type per stack.
Intentionally out of scope
City seed data: there's no authoritative open list. Cities are for apps to add.
Dhivehi names for states: none exist in the sources.
Map pickers.
For the user
Review the data and Dhivehi:
database/data/REVIEW.md: the 53 Dhivehi country names translated here, 5 replaced iso-codes names, atoll Dhivehi names, 7 council spellings that differ from the register, and one register category typo.
docs/advanced-usage/dhivehi-and-rtl.md#for-review: the strings.
Licensing: iso-codes' data is LGPL-2.1+. It's shipped as data next to the MIT code. Please confirm that's fine before release (docs/advanced-usage/data-sources.md#licences-and-attribution). The island register needs attribution, which is in the README and the docs.
Codecov: make sure CODECOV_TOKEN is available to this repo (uploads use fail_ci_if_error: false).
Release: tag and submit to Packagist. Once forms v2, manifest, permissions v3, stencil and translatable v3 are released, drop the VCS repositories. Once @javaabu/forms is on npm, change its npm dependency in LocationsServiceProvider and package.json.
Activity
n4ff4h commentedon Nov 5, 2026
Implementation plan
Studied: STANDARDS/PLAN,
../bulk-actionsand../permissions(structure, CI, route macros,ViewRenderer, React pages),../settings(stencil forms rendered by either stack), stencil (#14, custom fields), forms v2select2cascading (child,filter-field,ajax-url) and its React parity (useCascadeOptions), translatable's package-author contract (n4ff4h/translatable#10).Data and sources (#3)
Raw data lives in versioned JSON under
database/data/. Build scripts inbin/regenerate it from the sources, so every change is reviewable:data/iso_3166-1.json(it tracks the ISO OBP)iso_3166-1/dv.po(208 names). The rest are translated here and flagged for human reviewLD0002), English and Dhivehi names, category, atoll capital, coordinates, areaIslandList_20211101.csv(1,560 features)Unnamed features in the register are kept in the data file but not seeded by default.
Models and migrations (#2)
Country,State,City,Atoll,Island. All are config-swappable, with a table prefix option.name(English) anddhivehi_name, pluslocalizedName()for the current locale. The column isn't calledname_dvbecause that's translatable's suffix convention and would collide (Optional javaabu/translatable support #10).IslandCategoriesenum (residential, resort, industrial, institutional, uninhabited, …) with en/dv labels.Inputs (#4)
Route::locationOptions()adds a read-only JSON endpoint per type. It follows the forms select2 contract:filter[search],filter[country],filter[atoll],per_page=-1,fields.<x-locations::country-select>,atoll-select,island-select,state-select,city-select, and a cascading<x-locations::address-selects>. They wrap<x-forms::select2>withchildandfilter-field.CountrySelect,AtollSelect,IslandSelect(…) on@javaabu/formsSelect2, exported as@javaabu/locationsthrough manifest.CountrySelect::make()and friends, as custom stencil fields.Pages, controllers and permissions (#8, #9)
Route::locations([...])registers CRUD for countries, atolls, islands, states and cities. It takesguard,prefix,name,middleware,onlyandcontrollersoptions.locations::Countries/Index…). The stack is chosen withconfig('locations.views').view_/edit_/delete_{countries,atolls,islands,states,cities}, registered through javaabu/permissions, withResourcePolicysubclasses overridable from config orGate::policy().Optional translatable (#10)
The JSON storage contract applies. Package models ship translatable subclasses, chosen when translatable is enabled. Seeding writes the Dhivehi names into the translations. Tested with and without it, plus a CI leg without it.
Order of work
Open questions (decided, recorded here)
n4ff4h commentedon Nov 5, 2026
Final summary
javaabu/locations v1.0 is on
main. CI is green on the full matrix:There are 105 PHPUnit tests and 19 Vitest tests. Every
next-genissue is closed except #11 (needs a human).What was built
Country,State,City,Atoll,Island, configurable throughconfig('locations.models')andconfig('locations.tables').nameanddhivehi_name, withlocalizedName(). Islands also haveformattedName()(ހއ. ދިއްދޫ).IslandCategoriesenum, plus scopes (ofAtoll('HA'),inhabited(),search(),ordered()), factories, and restricted deletes.database/data, built bybin/build-data.phpfrom pinned source snapshots and checked bybin/validate-data.php. The sources are cited in docs/advanced-usage/data-sources.md.php artisan locations:seedandLocationsSeederonly add what's missing unless you pass--update.Route::locationOptions()).<x-locations::island-select parent="atoll_id">IslandSelectfrom@javaabu/locationsIslandSelect::make()->dependsOn('atoll_id')Route::locations()registers index, show, create, edit and delete for every type, in Blade or Inertia React (locations::Resources/*), and returns JSON on request.view_,edit_anddelete_{countries,states,cities,atolls,islands}through javaabu/permissions, with overridable policies.dhivehi_nameis kept in step.lang/dv.json, Thaana inputs, logical CSS only, and Thaana in the tests.llms.txt,AGENTS.md/CLAUDE.md, and the Laravel Boost guideline andjavaabu-locationsskill.Decisions worth knowing
dhivehi_name, notname_dv.name_dvwould collide with translatable's{field}_{locale}attributes.MLE. An atoll select that depends on a country lists the atolls only for the Maldives, so forms'fallbackcan show a city field for foreign addresses.Vilin'gili,Kurin'bi). The LGA's spellings are listed for review, not applied.Intentionally out of scope
For the user
database/data/REVIEW.md: the 53 Dhivehi country names translated here, 5 replaced iso-codes names, atoll Dhivehi names, 7 council spellings that differ from the register, and one register category typo.CODECOV_TOKENis available to this repo (uploads usefail_ci_if_error: false).LocationsServiceProviderandpackage.json.