Use the existing backend for French and Spanish

The existing controllers, repositories, conjugation tables, six person IDs, grading, phrase quota and phrase storage are reused. Select the learning language with verb_language. The existing language field still controls the interface and translations.

1 Deploy and add the user preference

After deploying these backend changes, run this from the Laravel backend directory:

php artisan migrate --path=database/migrations/2026_10_26_000001_add_verb_language_to_users_table.php --force

The user table has two language fields: the original language for the interface and verb_language for learning. A new preference defaults to fr. Existing choices are kept when the migration or architecture file is run again. For phpMyAdmin, reimport your usual remote 01_architecture.sql into taxiboucmydev. It now includes the preference and checks existing elements. For MAMP, use local 01_architecture.sql in conjugador. No separate preference SQL file is needed. The migration can subsequently record this change without adding the column twice.

In the existing GitHub Actions Artisan operation, the arguments are:

["migrate","--path=database/migrations/2026_10_26_000001_add_verb_language_to_users_table.php","--force"]

No extra import or OpenAI setting is required. Spanish verbs, tenses and bridge data must already be present in that same database.

2 Save the menu selection

POST /api/update-user
Authorization: existing user token

{"verb_language":"es"}

Use fr to switch back. Login and GET /api/user return the saved preference so another device can restore it. Existing mobile content endpoints default to French when verb_language is missing, protecting installed French-only clients even when the account chooses Spanish on another device. New mobile clients explicitly send verb_language on content requests and X-Verb-Language-Client: 1 on authentication requests. Login list payloads then use the saved account choice. The options endpoint and web callers may use the account default. An explicit request override does not save a different account preference. Unsupported values return validation errors.

3 Load pronouns and actual tense IDs

GET /api/learning-options

Authenticated response: verb_language, persons and tense_modes. Each person contains the existing id, a pronoun and a label. Each mood contains tense_mode_id, tense_mode and tenses; each tense contains tense_id and tense.

Spanish uses yo, tú, él/ella/usted, nosotros/nosotras, vosotros/vosotras and ellos/ellas/ustedes. Read Spanish tense IDs from this response; local and remote IDs may differ. When switching languages, replace selected tense IDs and clear exercises/verb lists cached for the previous language. The mobile grammar adapter now uses this endpoint for Spanish and retains the existing constants for French. Empty or invalid Spanish options block Spanish practice with a retry action.

4 Load Spanish default lists by key

POST /api/get-verbs-list
Authorization: existing user token

{"verb_language":"es","list_type":"official","list_key":"beginner"}
list_keySpanish verbs selected
beginnerSaved Spanish official beginner list: first 25 ranked verb records
elementarySaved Spanish official elementary list: next 25 ranked verb records
intermediateSaved Spanish official intermediate list: next 50 ranked verb records
irregularirregular = 1, including unranked verbs

Import remote file 07_spanish_default_lists.sql after the Spanish frequency import to save these three groups in the existing list and list_verbs tables. See the short import guide or use the local MAMP file. No architecture change is required. The backend resolves each saved list by language_code = es, official = 1 and name = list_key. The names are stable keys, so keep them unchanged. IDs may differ across databases. Multiple matching official rows return HTTP 409 instead of selecting an arbitrary list.

The SQL counts actual ranked Spanish records: positions 1–25, 26–50 and 51–100, sorted by frequency then verb ID. This produces exactly 25, 25 and 50 memberships despite missing corpus ranks or ties. Rank 0 is excluded. Rerun file 07 after future verb/frequency imports to refresh these saved memberships. Until each saved list is imported, its key retains the previous frequency-band fallback (corpus ranks 1–25, 26–50 or 51–100). The irregular key continues to read Spanish irregular flags directly.

Send the same three fields to POST /api/request-verbs-test (fill-in and voice), POST /api/request-verbs-test-cards and POST /api/request-verbs-test-all-persons, along with their existing tense IDs and exercise counts. No list_id is needed; a supplied key takes precedence over it. An unknown or empty Spanish key, or a Spanish key without list_type: "official", returns HTTP 422 rather than falling back to random practice.

This key support applies only when verb_language: "es" is explicitly sent. French requests retain existing ID and membership behavior. Requests without a key retain their existing behavior, including the numeric Spanish aliases and personal list IDs. The current mobile app still sends numeric IDs; switching it to these keys is a separate frontend step. Personal lists continue to use list_id and omit list_key.

What changes in the existing endpoints

AreaBehavior
Lists, search and random verbsSelect verbs in the learning language. Spanish verbs with frequency 0 are included; French keeps its existing ranked-verb rule.
Official listsSpanish keys beginner, elementary and intermediate read saved official memberships when present; irregular reads Spanish flags. Legacy numeric collections 1–4 remain supported and reuse the same selectors. Browsing and every exercise mode share these rules. Other official collections may reuse linked equivalents. Unknown/zero ranks follow ranked verbs in browsing; random exercises keep random ordering.
Personal lists and account creationEmpty and populated personal lists save language_code and are returned only in that language. Additions require matching verb/list language and ownership. New device, email, Apple and Google accounts save the onboarding verb_language; existing-account login preserves its saved choice.
French translations of Spanish verbsUse the French infinitives linked through verb_equivalents. Missing localized translations fall back to the existing English gloss.
Conjugation tablesSpanish tense entries keep the existing persons array of forms and add a parallel person_ids array. Use these IDs to label sparse imperatives correctly instead of assuming array position 0 is yo.
Exercises and correctionsReuse the existing flow. Spanish imported forms already include haber and reflexive pronouns; French auxiliary assembly is retained only for French.
Phrase generationThe conjugation's verb record determines French or Spanish. The same service, model, complexity limits, quota and phrase storage are used. Spanish mistake explanations use Spanish grammar while keeping the user's explanation language.
Phrase translationThe learning language is the source; the user's interface language is the target. When both are identical, English remains the fallback.
Spanish progress in get-verbs-listReturns progress_by_tense and progress_by_mood, keyed by actual database IDs, both per verb and for the complete returned list. Each entry has total, learned and percentage_progress. Counts use existing forms, including sparse imperatives. French retains its existing statistics fields.
AccentsThe existing accent preference also covers á, í, ó and ú. ñ remains a distinct letter.

The mobile integration is implemented in the sibling app repository; see mobile implementation notes and implementation results and release checks. Deploy these focused backend prerequisites before testing that mobile build against the API. Import the previously prepared Spanish irregular flags SQL for remote development, or its local version. It sets 243 positive flags using the selected RAE/ASALE NGLE definition. Unlisted Spanish verbs remain unchanged; zero is not a verified regular classification. No new architecture or list population is needed. No remote deployment or database import was performed during this implementation. Frequency values come from the separately prepared Spanish frequency import, not French equivalents.