Visitar URL original
Defects in English language files · Issue #5366 · ExpressionEngine/ExpressionEngine · GitHub
Skip to content

Defects in English language files #5366

Open

Description

@Caleydon

ExpressionEngine 7.5.26 - Defects in English language files

Reported by: Slovak translation maintainer
Version audited: ExpressionEngine 7.5.26 (unmodified) + Forum 5.1.3, IP to Nation 3.0.0, Simple Commerce 2.2.1
Verified on: PHP 8.1.6
Scope: 74 English *_lang.php files (9,831 keys) cross-referenced against 2,708 PHP/HTML source files.


How this was found

While maintaining the Slovak translation I compared every English language string against its actual consumption in the codebase. Each key containing a placeholder was traced to its lang() / ee()->lang->line() call sites and classified by the substitution mechanism used there.

This surfaced a class of defects that a translator hits immediately but that is invisible when reading the language files alone: ExpressionEngine uses two unrelated placeholder conventions, and a handful of strings use the wrong one for their call site.

Convention Mechanism Example call site
%s, %d sprintf() sprintf(lang('key'), $val)
%x, %y, %n, %time%, %max%, %overflow_names% str_replace() str_replace('%x', $val, lang('key'))

For the record: %x is legitimate in EE and must not be "corrected" to %s. Of 34 %x occurrences, 26 are correctly consumed by str_replace, 7 are in dead keys, and exactly 1 is a genuine bug (§2.1).

All findings below were reproduced by executing the relevant sprintf calls with representative arguments.


1. Critical - fatal errors on PHP 8

1.1 version_update_inst - ValueError, breaks Settings › General

String: system/ee/language/english/expressionengine_info_lang.php:27

'version_update_inst' => '<a class="update" href="%d" title="Download Here" rel="external">Download the latest version</a> and follow the <a class="update" href="%i" title="Update Instructions" rel="external">Update Instructions</a>.',

Call site: system/ee/ExpressionEngine/Controller/Settings/General.php:336

$banner->addToBody(sprintf(lang('version_update_inst'), $upgrade_url, $instruct_url));

Two defects:

  1. %i is not a valid PHP format specifier. On PHP 8 sprintf() throws:
    ValueError: Unknown format specifier "i"
    
  2. %d casts its argument to integer. A URL string becomes 0, producing href="0".

Reproduction:

sprintf(
    '<a href="%d">Download</a> and <a href="%i">Instructions</a>',
    'https://expressionengine.com/download',
    'https://docs.expressionengine.com/update'
);
// PHP 8.1.6 → ValueError: Unknown format specifier "i"

Impact: Any user visiting Settings › General while an update is available gets a fatal error instead of the update banner. On PHP 7 the page rendered but with a broken download link (href="0").

Suggested fix: use %s for both placeholders.


1.2 channel_form_lang.php - four validation strings expect 2 arguments, receive 1

Strings: system/ee/language/english/channel_form_lang.php

Line Key Value
24 exact_length The %s field must be exactly %s characters in length.
38 matches The %s field does not match the %s field.
40 max_length The %s field cannot exceed %s characters in length.
42 min_length The %s field must be at least %s characters in length.

Call sites: system/ee/legacy/libraries/Form_validation.php

// line 815 - one argument (field label)
$message = sprintf($line, $this->_translate_fieldname($row['label']));

// line 951 - one argument (rule parameter)
$message = sprintf($line, $param);

Form_validation never passes more than one argument. On PHP 8:

ValueError: The arguments array must contain 2 items, 1 given

Compounding issue - key collision. The same four keys also exist in system/ee/language/english/form_validation_lang.php (lines 19, 47, 49, 51) with the correct single-placeholder form:

'max_length' => 'This field cannot exceed %s characters in length.',

Whichever file is loaded last wins, so the failure is load-order dependent and therefore intermittent across contexts. Form_validation.php:1146 explicitly loads form_validation, which usually masks the problem - but any code path that loads channel_form afterwards will crash.

Suggested fix: drop the leading The %s field from the channel_form_lang.php variants, or remove the duplicated keys entirely and rely on form_validation_lang.php.


2. Incorrect output

2.1 sig_too_big - number rendered as hexadecimal

String: system/ee/language/english/member_lang.php:545

'sig_too_big' => 'Signatures can only contain %x characters',

Call site: system/ee/ExpressionEngine/Addons/member/mod.member_images.php:107

sprintf(lang('sig_too_big'), $maxlength)

This is the one place where %x reaches sprintf instead of str_replace. %x is lowercase hexadecimal, so:

sprintf('Signatures can only contain %x characters', 10000);
// → "Signatures can only contain 2710 characters"

Impact: users are told a wrong, much smaller-looking limit. With the default sig_maxlength of 10000 the message reads "2710 characters".

Suggested fix: change %x to %s in the string (the call site already uses sprintf consistently with the rest of the file).


2.2 less_than - key collision corrupts relative dates

Defined twice with unrelated meanings:

File Line Value
system/ee/language/english/core_lang.php 237 less than
system/ee/language/english/form_validation_lang.php 41 This field must be less than: %s

Consumer: system/ee/legacy/libraries/Relative_date.php:43

$this->less_than = lang('less_than');

Relative_date wants the core_lang value to build strings like "less than a minute ago". If form_validation_lang.php has been loaded, the output becomes:

This field must be less than: %s a minute ago

Suggested fix: rename one of them (e.g. validation_less_than).


2.3 running_current - %v is never substituted

String: system/ee/language/english/expressionengine_info_lang.php:19

'running_current' => 'ExpressionEngine is up to date (%v)',

Call site: system/ee/ExpressionEngine/Controller/Settings/General.php:347

->withTitle(lang('running_current'))

No sprintf, no str_replace. The literal %v is shown to the user. The very next line handles the sibling string correctly:

->addToBody(sprintf(lang('running_current_desc'), APP_VER))

Suggested fix: either wrap the title in sprintf(..., APP_VER) and change %v to %s, or drop the placeholder from the string.


3. Missing language keys

Key Called from Notes
search_reindex_completed system/ee/ExpressionEngine/Service/Channel/Reindex.php:195 Typo in the code. search_reindexed_completed exists at utilities_lang.php:553 (Content reindexing complete. %s entries were reindexed.) and already carries the %s this call passes.
removed_role system/ee/ExpressionEngine/Controller/Members/Roles/Roles.php:2006 Not defined anywhere. sprintf(lang('removed_role'), '<b>'.$role_name.'</b>') writes the literal string removed_role into the CP action log.

4. Values passed by code but discarded by the string

The call site supplies data the English string has no placeholder for, so the information is silently dropped.

Key Language file Call site Discarded value
file_saved admin_lang.php:389 Controller/Utilities/Translate.php:479 destination path
rows_replaced admin_lang.php:753, tools_data_lang.php Controller/Utilities/Sandr.php:63 number of replacements
imglib_unsafe_config image_lib_lang.php:25 legacy/libraries/Image_lib.php:685 offending path
forum_board_removed_desc forum/…/forum_cp_lang.php:383 user/addons/forum/mcp.forum.php:1223 forum name
access_privilege_caution members_lang.php:482 Controller/Members/Profile/Roles.php:87 icon markup (already hardcoded in the string)
rel_ft_limit fieldtypes_lang.php:66 Addons/relationship/ft.relationship.php:709 second argument (string has one %s, two are passed)

Worth noting that utilities_lang.php already has the correct variants - file_saved at line 119 (The translation file has been saved to <b>%s</b>) and rows_replaced at line 205 (… occurred: %s). The duplicates in admin_lang.php and tools_data_lang.php are the stale ones.


5. Dead keys containing placeholders

Not referenced anywhere in the codebase. Listed because they mislead translators into preserving placeholders in strings that will never render.

Key File Placeholder
unreadable_language installer_lang.php:97 %x
click_to_recount admin_lang.php %x, %y
system_path admin_lang.php %x
auto_redirection core_lang.php %x
page_rendered cp_lang.php %x
queries_executed cp_lang.php %x
if_replacing_templates tools_data_lang.php %x
entry_title_with_title content_lang.php %title

6. Duplicate keys across files

431 keys are defined in more than one English language file with differing values. Resolution depends on load order, which makes the effective string context-dependent.

27 of these are risky - one variant carries a placeholder and another does not, so depending on load order either a value is dropped or sprintf raises ArgumentCountError / ValueError on PHP 8.

Highest-impact clusters:

  • channel_form_lang.php × form_validation_lang.php - 13 validation keys (required, numeric, integer, alpha, alpha_dash, alpha_numeric, is_natural, is_natural_no_zero, is_numeric, valid_email, exact_length, max_length, min_length, matches)
  • admin_lang.php × utilities_lang.php × tools_data_lang.php - file_saved, rows_replaced, total_results
  • addons_lang.php × rte_lang.php - settings_saved_desc

7. Spelling and markup errors in English strings

File Key Current Should be
installer_lang.php:100 unreadable_dbdriver sytem/ee/installer/schema/ system/…
installer_lang.php:103 update_step_error occured occurred
admin_lang.php:145 analize Analize Tables - the key itself is misspelled too analyze / Analyze Tables
admin_lang.php:551 not_writable_path writeable writable
cp_lang.php:432 not_writable_path writeable writable
members_lang.php:346 not_writable_path writeable writable
tools_lang.php:75 trans_file_not_writable writeable writable
utilities_lang.php:129 trans_file_not_writable writeable writable
members_lang.php:438 your_password_anonymize_members_desc anomymize anonymize
upload_lang.php:15 upload_invalid_dimensions exceedes exceeds
core_lang.php:166 php72_intl_error extentsion extension
cp_lang.php:895 text_icon Text file iconn icon
sites_lang.php:31 invalid_short_name underscroes underscores
cli_lang.php:257 command_make_fieldtype_building_fieldtype Building fieldype. fieldtype
cli_lang.php:493 command_sync_conditional_fields_summary connditional conditional
ip_to_nation_lang.php:35 ip_db_failed IP databse update failed database

Unbalanced HTML - a stray </em> with no opening tag, which leaks into the rendered page:

  • system/user/addons/forum/language/english/forum_cp_lang.php:247 - reply_notification_desc
    … when a <b>new</b> reply is made.</em><em>Separate multiple e-mails …
    
  • system/user/addons/ip_to_nation/language/english/ip_to_nation_lang.php:63 - update_info
    … rel="external">maxmind.com</a>.</em><em><i>The new data will …
    

Summary by severity

Severity Count Items
Critical (fatal on PHP 8) 2 §1.1 version_update_inst, §1.2 channel_form_lang.php ×4 keys
High (wrong output shown to users) 3 §2.1 sig_too_big, §2.2 less_than, §2.3 running_current
Medium (missing keys / lost data) 8 §3 ×2, §4 ×6
Low (dead keys, duplicates, spelling) 24+ §5 ×8, §6, §7 ×16

Notes for other translators

Three things that are easy to get wrong when translating EE and are worth documenting upstream:

  1. %x, %y, %n and %name% tokens are not sprintf placeholders. They must be preserved verbatim; substituting %s breaks them. Only %s and %d go through sprintf.
  2. Validation strings should carry exactly one %s. Form_validation always passes a single argument.
  3. Where the English string is missing a placeholder that the code supplies (§4), a translation may legitimately add %s - the Slovak files already do this for file_saved and rows_replaced, matching utilities_lang.php rather than the stale admin_lang.php copies.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions