Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bb2a530
Add automatic provisioning of l10n tables for database objects
BurntimeX Jul 23, 2026
cf52b79
Migrate captcha questions from i18n phrases to l10n storage
BurntimeX Jul 24, 2026
1488728
Support per-column language sets in L10n storage
BurntimeX Jul 25, 2026
729372d
Fix PHPStan issue
BurntimeX Jul 25, 2026
17022d5
Refactor user options to `DatabaseObjectBuilder`
BurntimeX Jul 26, 2026
20b0dec
Migrate user options from i18n phrases to l10n storage
BurntimeX Jul 30, 2026
80866cf
Ensure that dbo collections are never empty
BurntimeX Jul 30, 2026
58db157
Add a trait for l10n collections
BurntimeX Jul 30, 2026
94d5fd4
Fix handling of null values
BurntimeX Jul 31, 2026
102e6a6
Derive the `isPristine` flag automatically on write
BurntimeX Jul 31, 2026
0044175
Optimize `L10nCaptchaQuestionList`
BurntimeX Jul 31, 2026
b74ffe6
Use `L10nUserOptionList` in user option grid view
BurntimeX Jul 31, 2026
166afd9
Fix handling of null values
BurntimeX Jul 31, 2026
eecc1ac
Remove template scripting from user option descriptions
BurntimeX Jul 31, 2026
2a232e0
Use `UserOptionBuilder` in the user option importer
BurntimeX Jul 31, 2026
ac5f298
Introduce `L10nValue` type alias for localized value arrays
dtdesign Sep 23, 2026
fd00583
Migrate reaction types from i18n phrases to l10n storage
dtdesign Sep 28, 2026
9fea522
Remove leftover call to the deleted `SyncL10nTables` command
dtdesign Sep 28, 2026
153129c
Render reaction type titles as text to prevent HTML injection in the …
dtdesign Sep 28, 2026
6369508
Keep monolingual and per-language l10n values exclusive during synchr…
dtdesign Sep 28, 2026
7a4f831
Migrate user ranks from i18n phrases to l10n storage
dtdesign Sep 28, 2026
4c3ffe5
Migrate cronjob descriptions from i18n phrases to l10n storage
dtdesign Sep 28, 2026
e010580
Migrate label group titles from i18n phrases to l10n storage
dtdesign Sep 29, 2026
14d78f8
Add l10n support to EmailFormField
dtdesign Sep 29, 2026
b52fa28
Migrate contact recipients from i18n phrases to l10n storage
dtdesign Sep 29, 2026
c83166a
Add l10n support to WysiwygFormField
dtdesign Sep 29, 2026
bc97031
Ignore non-string and unknown-language input in I18nHandler::readValu…
dtdesign Sep 29, 2026
fbc2cf6
Skip the `_i18n` form data processor for l10n fields
dtdesign Sep 29, 2026
fbad2d8
Migrate paid subscriptions from i18n phrases to l10n storage
dtdesign Sep 29, 2026
fdbcbeb
Render user option titles and descriptions from l10n storage in share…
dtdesign Oct 1, 2026
5fab0ba
Prefer administrator-customized phrase values when syncing l10n storage
dtdesign Oct 1, 2026
8e3510f
Merge user option and captcha question l10n schema changes into updat…
dtdesign Oct 1, 2026
b14c96f
Keep pristine per-language l10n rows when a single-language form resu…
dtdesign Oct 1, 2026
1d17654
Truncate language variable values to the l10n column length on migrat…
dtdesign Oct 1, 2026
1d1737c
Drop NULL column values when reading l10n storage, fixing builder upd…
dtdesign Oct 1, 2026
41c7c9e
Sync l10n storage when a language is added or imported and flush cach…
dtdesign Oct 1, 2026
be2d352
Fix double-encoded rank title in user rank grid view
dtdesign Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions com.woltlab.wcf/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@
The following steps require the new files and must run after the `objectType` PIP:
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_userRankImage.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_reactionTypeIcon.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_reactionTypeL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_userRankL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_labelGroupL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_contactRecipientL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_paidSubscriptionL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_userOptionL10n.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_captchaQuestionL10n.php</instruction>
<instruction type="database" run="standalone">acp/database/update_com.woltlab.wcf_6.3_step3.php</instruction>

The following step requires the new files and must run before the `cronjob` PIP, which precedes the `objectType` PIP:
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_cronjobL10n.php</instruction>

`acp/database/update_com.woltlab.wcf_6.3_step3.php` must run after all of the scripts above, including `acp/update_com.woltlab.wcf_6.3_cronjobL10n.php`.
-->
</package>
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/messageSidebar.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
{if $userProfile->getUserOption($__sidebarUserOption)}
{assign var='__formattedUserOption' value=$userProfile->getFormattedUserOption($__sidebarUserOption)}
{if $__formattedUserOption}
<dt>{lang}wcf.user.option.{$__sidebarUserOption}{/lang}</dt>
<dt>{$userProfile->getUserOptionTitle($__sidebarUserOption)}</dt>
<dd>{unsafe:$__formattedUserOption}</dd>
{/if}
{/if}
Expand Down
4 changes: 2 additions & 2 deletions com.woltlab.wcf/templates/optionFieldList.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
{icon name='bolt'}
</span>
{/if}
{lang}{$langPrefix}{$option->optionName}{/lang}
{if $optionData[title]|isset}{$optionData[title]}{else}{lang}{$langPrefix}{$option->optionName}{/lang}{/if}
</label>
{/if}
</dt>
Expand All @@ -29,7 +29,7 @@
{/if}
</small>
{/if}
<small>{lang __optional=true}{$langPrefix}{$option->optionName}.description{/lang}</small>
<small>{if $optionData[description]|isset}{$optionData[description]}{else}{lang __optional=true}{$langPrefix}{$option->optionName}.description{/lang}{/if}</small>
</dd>
</dl>
{/foreach}
6 changes: 5 additions & 1 deletion com.woltlab.wcf/templates/shared_wysiwygFormField.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
*}{/if}{*
*}{/if}{*
*}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
*}>{$field->getValue()}</textarea>
*}>{if !$field->isL10n() || !$field->hasI18nValues() || $availableLanguages|count === 1}{$field->getValue()}{/if}</textarea>

{include file='shared_wysiwyg' wysiwygSelector=$field->getPrefixedId()}

{if $field->isL10n()}
{include file='shared_multipleLanguageInputJavascript'}
{/if}
4 changes: 2 additions & 2 deletions com.woltlab.wcf/templates/userOptionFieldList.tpl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{foreach from=$options item=optionData}
{assign var=option value=$optionData[object]}
<dl class="{$option->optionName}Input{if $errorType|is_array && $errorType[$option->optionName]|isset} formError{/if}">
<dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>{if $isSearchMode|empty || !$optionData[hideLabelInSearch]}<label for="{$option->optionName}">{$langPrefix|concat:$option->optionName|phrase}</label>{if $isSearchMode|empty && $option->required} <span class="customOptionRequired">*</span>{/if}{/if}</dt>
<dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>{if $isSearchMode|empty || !$optionData[hideLabelInSearch]}<label for="{$option->optionName}">{$option->getTitle()}</label>{if $isSearchMode|empty && $option->required} <span class="customOptionRequired">*</span>{/if}{/if}</dt>
<dd>{unsafe:$optionData[html]}
<small>{lang __optional=true}{$langPrefix}{$option->optionName}.description{/lang}</small>
<small>{$option->getDescription()}</small>

{if $errorType|is_array && $errorType[$option->optionName]|isset}
<small class="innerError">
Expand Down
4 changes: 2 additions & 2 deletions com.woltlab.wcf/templates/userProfileOptionFieldList.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{assign var=error value=''}
{/if}
<dl class="{$option->optionName}Input{if $error} formError{/if}">
<dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{$langPrefix|concat:$option->optionName|phrase}</label></dt>
<dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{$option->getTitle()}</label></dt>
<dd>{unsafe:$optionData[html]}
{if $error}
<small class="innerError">
Expand All @@ -17,7 +17,7 @@
{/if}
</small>
{/if}
<small>{lang __optional=true}{$langPrefix}{$option->optionName}.description{/lang}</small>
<small>{$option->getDescription()}</small>
</dd>
</dl>
{/foreach}
12 changes: 0 additions & 12 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,6 @@ parameters:
count: 11
path: wcfsetup/install/files/lib/acp/form/PageEditForm.class.php

-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
identifier: empty.notAllowed
count: 3
path: wcfsetup/install/files/lib/acp/form/PaidSubscriptionAddForm.class.php

-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
identifier: empty.notAllowed
Expand Down Expand Up @@ -457,12 +451,6 @@ parameters:
count: 1
path: wcfsetup/install/files/lib/data/comment/response/CommentResponseCollection.class.php

-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
identifier: empty.notAllowed
count: 2
path: wcfsetup/install/files/lib/data/contact/recipient/ContactRecipient.class.php

-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
identifier: empty.notAllowed
Expand Down
2 changes: 1 addition & 1 deletion ts/WoltLabSuite/Core/Component/Reaction/Button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class ReactionPopover {

const reactionTypeButtonTitle = document.createElement("span");
reactionTypeButtonTitle.className = "reactionTypeButtonTitle";
reactionTypeButtonTitle.innerHTML = reactionType.title;
reactionTypeButtonTitle.textContent = reactionType.title;
reactionTypeButton.appendChild(reactionTypeButtonTitle);

reactionTypeButton.addEventListener("click", () => this.#click(reactionType.reactionTypeID));
Expand Down
30 changes: 30 additions & 0 deletions ts/WoltLabSuite/Core/Form/Builder/Field/CkeditorI18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Data handler for CKEditor with l10n support.
*
* @author Alexander Ebert
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/

import ValueI18n from "./ValueI18n";
import { FormBuilderData } from "../Data";
import { dispatchToCkeditor } from "WoltLabSuite/Core/Component/Ckeditor/Event";
import { getCkeditorById } from "WoltLabSuite/Core/Component/Ckeditor";

export class CkeditorI18n extends ValueI18n {
protected _getData(): FormBuilderData {
// CKEditor does not permanently mirror the contents to the <textarea>.
(this._field as HTMLTextAreaElement).value = getCkeditorById(this._fieldId)!.getHtml();

return super._getData();
}

destroy(): void {
super.destroy();

dispatchToCkeditor(this._field!).destroy();
}
}

export default CkeditorI18n;
Loading
Loading