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
{{ message }}
Repository navigation
refactor(Core/Locale): Move acore_string to rows (breaking change) - #28281
Draft. This is the follow-up agreed in PR 28081 and PR 27958: acore_string stores its translations as rows in acore_string_locale, like every other *_locale table, instead of one column per language. It stays a draft until we have the list of modules that still write to the locale_* columns of acore_string (see Known Issues), so they can move to the table together with this change. It is not built or tested in-game yet.
acore_string was the only localized table with one column per language. After PR 28081 a module that registers a new locale had to add its own locale_<code> column to a core table, and the loader had to look up in information_schema which columns exist before it could build its query. PR 27958 added acore_string_locale (entry, locale, content) next to the columns; this PR finishes the move, as discussed with Helias:
The pending world SQL moves the non-empty texts of the eight client locales (koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU) from acore_string to acore_string_locale and drops the eight columns. acore_string keeps entry and content_default (enUS).
A non-empty row that is already in acore_string_locale for one of these locales wins over the column, because that is what the loader picked until now. The SQL first copies such rows into the column, then replaces the rows of these locales with the column values. Empty strings and NULL are not copied, the loader skipped them too. Rows of other locales (e.g. a module's plPL) and rows whose entry is not in acore_string are not touched.
LoadAcoreStrings() reads enUS from acore_string and every other locale from acore_string_locale. The information_schema lookup and the query built at runtime are gone.
For a server without modules nothing changes in-game: every text the server loaded before the migration is loaded after it, from the table.
From now on a new or changed translation of an acore string is a DELETE + INSERT into acore_string_locale instead of an UPDATE of a locale_* column, the same as for the other *_locale tables.
AI-assisted Pull Requests
Important
Using AI tools to prepare pull requests is allowed, but it must be disclosed and it must follow our AC guidelines for AI Agentic Engineering (link below).
You are expected to fully understand the changes you submit and to be able to explain and justify them when maintainers ask.
AI tools (e.g. Claude, ChatGPT, or similar) were used entirely or partially to prepare this pull request. If checked, specify which tools and models below.
Tools/models used: Claude Code, Claude Opus 5.5 (claude-opus-5-5) produced the changes and this description. The self-review report will be added in a comment before the PR leaves draft.
None, there is no linked issue. Follow-up of PR 28081 and PR 27958, agreed with Helias there and on Discord.
SOURCE:
The changes have been validated through:
Live research (checked on live servers, e.g Classic WotLK, Retail, etc.)
Sniffs (remember to share them with the open source community!)
Video evidence, knowledge databases or other public sources (e.g forums, Wowhead, etc.)
The changes promoted by this pull request come partially or entirely from another project (cherry-pick). Cherry-picks must be committed using the proper --author tag in order to be accepted, thus crediting the original authors, unless otherwise unable to be found
None of the above applies: this is a schema change with no game data, and nothing is taken from another project.
Tests Performed:
This PR has been:
Tested in-game by the author.
Tested in-game by other community members/someone else other than the author/has been live on production servers.
This pull request requires further testing and may have edge cases to be tested.
Not built and not tested in-game yet (draft). So far:
Both codestyle checks pass (codestyle-sql.py, codestyle-cpp.py).
The SQL was run on MySQL 8.4 against the base acore_string plus acore_string_locale, with extra rows for the edge cases: a table row where the column is NULL, a table row next to a different column value, an empty table row next to a column value, an empty column, a row whose entry is not in acore_string, and plPL rows. For every entry and each of the eight locales, the text the loader would pick before the migration (non-empty row, else non-empty column) equals the row after it: 4142 texts, no missing and no extra rows. The orphan row and the plPL rows were left as they were.
How to Test the Changes:
This pull request can be tested by following the reproduction steps provided in the linked issue
This pull request requires further testing. Provide steps to test your changes. If it requires any specific setup e.g multiple players please specify it as well.
Before applying the SQL, log in with a non-enUS client (e.g. deDE or esES) and note a few acore strings, e.g. .server info and the reply to a GM command run without a target.
Apply the pending SQL and start the server: acore_string has only entry and content_default, the startup log loads the same number of acore strings as before, and Errors.log has no acore_string_locale errors.
With the same client the texts from step 1 are still translated; an enUS client gets the English ones.
Change a row in acore_string_locale for that locale and run .reload acore_string: the new text is shown.
Known Issues and TODO List:
Update the modules that write to the locale_* columns of acore_string (translation modules, modules that ship their own acore strings with translations). After this PR their SQL fails on the missing column, and a column that a module added for its own locale (the PR 28081 way) is no longer read. They need to write rows to acore_string_locale instead. The list of affected repositories is in a comment below.
Merge only when every module that writes to these columns has its change to acore_string_locale ready (a PR or a branch in each module), so they can be updated at the same time and none of them breaks in between.
Build, in-game test and self-review report before the PR leaves draft.
The base dump data/sql/base/db_world/acore_string.sql still has the columns; it is not edited by hand, the pending SQL applies on top of it.
How to Test AzerothCore PRs
When a PR is ready to be tested, it will be marked as [WAITING TO BE TESTED].
You can help by testing PRs and writing your feedback here on the PR's page on GitHub. Follow the instructions here:
REMEMBER: when testing a PR that changes something generic (i.e. a part of code that handles more than one specific thing), the tester should not only check that the PR does its job (e.g. fixing spell XXX) but especially check that the PR does not cause any regression (i.e. introducing new bugs).
For example: if a PR fixes spell X by changing a part of code that handles spells X, Y, and Z, we should not only test X, but we should test Y and Z as well.
Move the texts of the eight client locales from the locale_* columns
of acore_string to acore_string_locale and drop the columns, so every
language, including the ones a module registers, is stored the same
way as in the other *_locale tables.
A non-empty row that was already in acore_string_locale is kept over
the column, as the loader did. LoadAcoreStrings() now reads enUS from
acore_string and all other locales from the table, so it no longer
needs to look up the existing columns in information_schema.
Repositories of the azerothcore organization that need a change before this PR can be merged (default branches, checked 2026-10-27). They write to the locale_* columns of acore_string, which this PR drops:
mod-locale-itIT: adds its own locale_itIT column (data/sql/db_world/base/00_acore_string_itIT.sql) and fills it with UPDATE (data/sql/db_world/updates/rev_1793007124.sql).
skeleton-module: data/sql/db-world/skeleton_module_acore_string.sql inserts into the eight columns. It is the template for new modules, so the example should show acore_string_locale.
mod-arenaspectator, mod-pvp-quests: a leftover copy of the skeleton file above (data/sql/db-world/base/skeleton_module_acore_string.sql, "Hello World from Skeleton-Module!"); it can be removed.
Keira3: the acore_string editor reads and writes the eight columns (libs/shared/acore-world-model/src/entities/acore-text.type.ts, libs/features/texts/src/acore-string/).
Fine as they are (only entry and content_default, or only DELETE): mod-azerothshard, mod-chromie-xp, mod-morph-all-players, mod-weekend-xp, mod-ptr-template, mod-transmog. Modules outside the organization were not checked.
If you know of other modules that write to these columns, please mention them here.
mod-locale-itIT: adds its own locale_itIT column (data/sql/db_world/base/00_acore_string_itIT.sql) and fills it with UPDATE (data/sql/db_world/updates/rev_1793007124.sql).
skeleton-module: data/sql/db-world/skeleton_module_acore_string.sql inserts into the eight columns. It is the template for new modules, so the example should show acore_string_locale.
mod-arenaspectator, mod-pvp-quests: a leftover copy of the skeleton file above (data/sql/db-world/base/skeleton_module_acore_string.sql, "Hello World from Skeleton-Module!"); it can be removed.
Keira3: the acore_string editor reads and writes the eight columns (libs/shared/acore-world-model/src/entities/acore-text.type.ts, libs/features/texts/src/acore-string/).
The migration transfers non-empty values for eight locales from acore_string columns into acore_string_locale rows, then drops those columns. LoadAcoreStrings now reads entry and content_default from acore_string and continues loading translations from acore_string_locale.
Priority: Low
Merge Risk: Low · up to 1300c
The migration is mergeable with bounded deployment awareness: apply world updates before using the new loader and avoid running older binaries against the migrated database. Whitespace-only translations still need preservation to avoid unintended default-text fallback.
- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Keep the old locale columns until the previous binary is retired.
The base LoadAcoreStrings selects locale_koKR through locale_ruRU from acore_string. This migration drops those columns. A restarted or rolled-back old binary can fail that query, so it cannot load acore strings and exits at startup. Redeploying the new binary avoids the failure.
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
Keep whitespace-only locale texts in the migration.
Under PAD SPACE, ' ' <> '' is false, so the migration drops whitespace-only texts. The loader kept them, because AddLocaleString skips only empty strings. Use LENGTH(...) > 0 in the eight UPDATE joins and the UNION ALL filters.
Keep reading existing locale_<code> columns in LoadAcoreStrings.
With world updates disabled through Updates.EnableDatabases, the pending migration does not run. LoadAcoreStrings then ignores non-empty locale_frFR values, so French clients receive the default text. The previous loader read those columns. Read existing locale_<code> columns again, and let acore_string_locale override them.
Helias
changed the title
refactor(Core/Locale): Move acore_string to rows
refactor(Core/Locale): Move acore_string to rows (breaking change)
Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CORERelated to the coreDBrelated to the SQL databasefile-cppUsed to trigger the matrix build
3 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Draft. This is the follow-up agreed in PR 28081 and PR 27958:
acore_stringstores its translations as rows inacore_string_locale, like every other*_localetable, instead of one column per language. It stays a draft until we have the list of modules that still write to thelocale_*columns ofacore_string(see Known Issues), so they can move to the table together with this change. It is not built or tested in-game yet.Changes Proposed:
This PR proposes changes to:
acore_stringwas the only localized table with one column per language. After PR 28081 a module that registers a new locale had to add its ownlocale_<code>column to a core table, and the loader had to look up ininformation_schemawhich columns exist before it could build its query. PR 27958 addedacore_string_locale(entry,locale,content) next to the columns; this PR finishes the move, as discussed with Helias:koKR,frFR,deDE,zhCN,zhTW,esES,esMX,ruRU) fromacore_stringtoacore_string_localeand drops the eight columns.acore_stringkeepsentryandcontent_default(enUS).acore_string_localefor one of these locales wins over the column, because that is what the loader picked until now. The SQL first copies such rows into the column, then replaces the rows of these locales with the column values. Empty strings and NULL are not copied, the loader skipped them too. Rows of other locales (e.g. a module'splPL) and rows whoseentryis not inacore_stringare not touched.LoadAcoreStrings()reads enUS fromacore_stringand every other locale fromacore_string_locale. Theinformation_schemalookup and the query built at runtime are gone.For a server without modules nothing changes in-game: every text the server loaded before the migration is loaded after it, from the table.
From now on a new or changed translation of an acore string is a
DELETE+INSERTintoacore_string_localeinstead of anUPDATEof alocale_*column, the same as for the other*_localetables.AI-assisted Pull Requests
Important
Using AI tools to prepare pull requests is allowed, but it must be disclosed and it must follow our AC guidelines for AI Agentic Engineering (link below).
You are expected to fully understand the changes you submit and to be able to explain and justify them when maintainers ask.
claude-opus-5-5) produced the changes and this description. The self-review report will be added in a comment before the PR leaves draft.Issues Addressed:
SOURCE:
The changes have been validated through:
None of the above applies: this is a schema change with no game data, and nothing is taken from another project.
Tests Performed:
This PR has been:
Not built and not tested in-game yet (draft). So far:
codestyle-sql.py,codestyle-cpp.py).acore_stringplusacore_string_locale, with extra rows for the edge cases: a table row where the column is NULL, a table row next to a different column value, an empty table row next to a column value, an empty column, a row whoseentryis not inacore_string, andplPLrows. For every entry and each of the eight locales, the text the loader would pick before the migration (non-empty row, else non-empty column) equals the row after it: 4142 texts, no missing and no extra rows. The orphan row and theplPLrows were left as they were.How to Test the Changes:
.server infoand the reply to a GM command run without a target.acore_stringhas onlyentryandcontent_default, the startup log loads the same number of acore strings as before, andErrors.loghas noacore_string_localeerrors.acore_string_localefor that locale and run.reload acore_string: the new text is shown.Known Issues and TODO List:
locale_*columns ofacore_string(translation modules, modules that ship their own acore strings with translations). After this PR their SQL fails on the missing column, and a column that a module added for its own locale (the PR 28081 way) is no longer read. They need to write rows toacore_string_localeinstead. The list of affected repositories is in a comment below.acore_string_localeready (a PR or a branch in each module), so they can be updated at the same time and none of them breaks in between.data/sql/base/db_world/acore_string.sqlstill has the columns; it is not edited by hand, the pending SQL applies on top of it.How to Test AzerothCore PRs
When a PR is ready to be tested, it will be marked as [WAITING TO BE TESTED].
You can help by testing PRs and writing your feedback here on the PR's page on GitHub. Follow the instructions here:
http://www.azerothcore.org/wiki/How-to-test-a-PR
REMEMBER: when testing a PR that changes something generic (i.e. a part of code that handles more than one specific thing), the tester should not only check that the PR does its job (e.g. fixing spell XXX) but especially check that the PR does not cause any regression (i.e. introducing new bugs).
For example: if a PR fixes spell X by changing a part of code that handles spells X, Y, and Z, we should not only test X, but we should test Y and Z as well.