Skip to content

Commit 2ab1020

Browse files
authored
Merge pull request #26 from LibreCodeCoop/chore/review-translations
chore: review translations
2 parents c3404a5 + 8d3b691 commit 2ab1020

14 files changed

Lines changed: 97 additions & 66 deletions

lib/Notification/Notifier.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ public function prepare(INotification $notification, string $languageCode): INot
4848
}
4949

5050
if ($notification->getMessage() === 'profile_field_updated_message') {
51+
// TRANSLATORS %1$s is the actor user ID, %2$s is the affected user ID, %3$s is the profile field label.
5152
$notification->setParsedMessage($l10n->t(
52-
'%1$s changed %2$s\'s %3$s profile field.',
53+
'%1$s changed profile field "%3$s" for user %2$s.',
5354
$notification->getMessageParameters(),
5455
));
5556
} elseif ($notification->getMessage() !== '' && $notification->getParsedMessage() === '') {

lib/Service/FieldValueService.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function upsert(
5353
$valueJson = $this->encodeValue($normalizedValue);
5454
$visibility = $currentVisibility ?? FieldExposurePolicy::from($definition->getExposurePolicy())->initialVisibility()->value;
5555
if (!FieldVisibility::isValid($visibility)) {
56-
throw new InvalidArgumentException($this->l10n->t('current_visibility is not supported'));
56+
throw new InvalidArgumentException($this->l10n->t('The provided visibility value is not supported.'));
5757
}
5858

5959
$entity = $this->fieldValueMapper->findByFieldDefinitionIdAndUserUid($definition->getId(), $userUid) ?? new FieldValue();
@@ -145,16 +145,17 @@ public function searchByDefinition(
145145
int $offset,
146146
): array {
147147
if ($limit < 1 || $limit > self::SEARCH_MAX_LIMIT) {
148+
// TRANSLATORS %d is the maximum supported search limit.
148149
throw new InvalidArgumentException($this->l10n->t('limit must be between 1 and %d', [self::SEARCH_MAX_LIMIT]));
149150
}
150151

151152
if ($offset < 0) {
152-
throw new InvalidArgumentException($this->l10n->t('offset must be greater than or equal to 0'));
153+
throw new InvalidArgumentException($this->l10n->t('The offset must be greater than or equal to 0.'));
153154
}
154155

155156
$normalizedOperator = strtolower(trim($operator));
156157
if (!in_array($normalizedOperator, [self::SEARCH_OPERATOR_EQ, self::SEARCH_OPERATOR_CONTAINS], true)) {
157-
throw new InvalidArgumentException($this->l10n->t('search operator is not supported'));
158+
throw new InvalidArgumentException($this->l10n->t('The search operator is not supported.'));
158159
}
159160

160161
$searchValue = $this->normalizeSearchValue($definition, $normalizedOperator, $rawValue);
@@ -177,12 +178,12 @@ public function searchByDefinition(
177178

178179
public function updateVisibility(FieldDefinition $definition, string $userUid, string $updatedByUid, string $currentVisibility): FieldValue {
179180
if (!FieldVisibility::isValid($currentVisibility)) {
180-
throw new InvalidArgumentException($this->l10n->t('current_visibility is not supported'));
181+
throw new InvalidArgumentException($this->l10n->t('The provided visibility value is not supported.'));
181182
}
182183

183184
$entity = $this->fieldValueMapper->findByFieldDefinitionIdAndUserUid($definition->getId(), $userUid);
184185
if ($entity === null) {
185-
throw new InvalidArgumentException($this->l10n->t('field value not found'));
186+
throw new InvalidArgumentException($this->l10n->t('No profile field value was found.'));
186187
}
187188

188189
$previousValue = $this->extractScalarValue($entity->getValueJson());
@@ -229,7 +230,7 @@ public function serializeForResponse(FieldValue $value): array {
229230
*/
230231
private function normalizeTextValue(array|string|int|float|bool $rawValue): array {
231232
if (is_array($rawValue)) {
232-
throw new InvalidArgumentException($this->l10n->t('text fields expect a scalar value'));
233+
throw new InvalidArgumentException($this->l10n->t('Text fields require a single text value.'));
233234
}
234235

235236
return ['value' => trim((string)$rawValue)];
@@ -241,12 +242,13 @@ private function normalizeTextValue(array|string|int|float|bool $rawValue): arra
241242
*/
242243
private function normalizeSelectValue(array|string|int|float|bool $rawValue, FieldDefinition $definition): array {
243244
if (!is_string($rawValue)) {
244-
throw new InvalidArgumentException($this->l10n->t('select fields expect a string value'));
245+
throw new InvalidArgumentException($this->l10n->t('Select fields require one of the configured option values.'));
245246
}
246247

247248
$value = trim($rawValue);
248249
$options = json_decode($definition->getOptions() ?? '[]', true);
249250
if (!in_array($value, $options, true)) {
251+
// TRANSLATORS %s is an invalid option value provided by the user.
250252
throw new InvalidArgumentException($this->l10n->t('"%s" is not a valid option for this field', [$value]));
251253
}
252254

@@ -259,7 +261,7 @@ private function normalizeSelectValue(array|string|int|float|bool $rawValue, Fie
259261
*/
260262
private function normalizeNumberValue(array|string|int|float|bool $rawValue): array {
261263
if (is_array($rawValue) || is_bool($rawValue) || !is_numeric($rawValue)) {
262-
throw new InvalidArgumentException($this->l10n->t('number fields expect a numeric value'));
264+
throw new InvalidArgumentException($this->l10n->t('Number fields require a numeric value.'));
263265
}
264266

265267
return ['value' => str_contains((string)$rawValue, '.') ? (float)$rawValue : (int)$rawValue];
@@ -272,7 +274,7 @@ private function encodeValue(array $value): string {
272274
try {
273275
return json_encode($value, JSON_THROW_ON_ERROR);
274276
} catch (JsonException $exception) {
275-
throw new InvalidArgumentException($this->l10n->t('value_json could not be encoded'), 0, $exception);
277+
throw new InvalidArgumentException($this->l10n->t('The stored value payload could not be encoded as JSON.'), 0, $exception);
276278
}
277279
}
278280

@@ -283,11 +285,11 @@ private function decodeValue(string $valueJson): array {
283285
try {
284286
$decoded = json_decode($valueJson, true, 512, JSON_THROW_ON_ERROR);
285287
} catch (JsonException $exception) {
286-
throw new InvalidArgumentException($this->l10n->t('value_json could not be decoded'), 0, $exception);
288+
throw new InvalidArgumentException($this->l10n->t('The stored value payload could not be decoded from JSON.'), 0, $exception);
287289
}
288290

289291
if (!is_array($decoded)) {
290-
throw new InvalidArgumentException($this->l10n->t('value_json must decode to an object payload'));
292+
throw new InvalidArgumentException($this->l10n->t('The stored value payload must decode to a JSON object.'));
291293
}
292294

293295
return $decoded;
@@ -328,13 +330,13 @@ private function normalizeSearchValue(FieldDefinition $definition, string $opera
328330
}
329331

330332
if (FieldType::from($definition->getType()) !== FieldType::TEXT) {
331-
throw new InvalidArgumentException($this->l10n->t('contains operator is only supported for text fields'));
333+
throw new InvalidArgumentException($this->l10n->t('The "contains" operator is only available for text fields.'));
332334
}
333335

334336
$normalized = $this->normalizeValue($definition, $rawValue);
335337
$value = $normalized['value'] ?? null;
336338
if (!is_string($value) || $value === '') {
337-
throw new InvalidArgumentException($this->l10n->t('contains operator requires a non-empty text value'));
339+
throw new InvalidArgumentException($this->l10n->t('The "contains" operator requires a non-empty text value.'));
338340
}
339341

340342
return ['value' => $value];

lib/Workflow/CreateTalkConversationProfileFieldChangeOperation.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function isAvailableForScope(int $scope): bool {
5555
#[\Override]
5656
public function validateOperation(string $name, array $checks, string $operation): void {
5757
if (trim($operation) !== '') {
58-
throw new \UnexpectedValueException($this->l10n->t('This workflow operation does not accept custom configuration'));
58+
throw new \UnexpectedValueException($this->l10n->t('This workflow operation does not support custom configuration.'));
5959
}
6060
}
6161

@@ -90,7 +90,8 @@ public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatch
9090
}
9191

9292
$this->broker->createConversation(
93-
$this->l10n->t('Profile field change: %1$s for %2$s', [
93+
// TRANSLATORS %1$s is the profile field label, %2$s is the affected user ID.
94+
$this->l10n->t('Profile field changed: %1$s for user %2$s', [
9495
$fieldLabel,
9596
$subject->getUserUid(),
9697
]),

lib/Workflow/NotifyAdminsOrGroupsProfileFieldChangeOperation.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function isAvailableForScope(int $scope): bool {
5757
public function validateOperation(string $name, array $checks, string $operation): void {
5858
$config = $this->parseConfig($operation);
5959
if ($config === null || $this->resolveRecipientUids($config['targets']) === []) {
60-
throw new \UnexpectedValueException($this->l10n->t('A valid target list is required'));
60+
throw new \UnexpectedValueException($this->l10n->t('A valid recipient list is required.'));
6161
}
6262
}
6363

@@ -85,8 +85,9 @@ public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatch
8585

8686
foreach ($this->resolveRecipientUids($config['targets']) as $recipientUid) {
8787
$subjectText = $this->l10n->t('Profile field updated');
88+
// TRANSLATORS %1$s is the actor user ID, %2$s is the affected user ID, %3$s is the profile field label.
8889
$messageText = $this->l10n->t(
89-
'%1$s changed %2$s\'s %3$s profile field.',
90+
'%1$s changed profile field "%3$s" for user %2$s.',
9091
[
9192
$subject->getActorUid(),
9293
$subject->getUserUid(),

src/components/AdminSupportBanner.vue

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-or-later
77
<NcNoteCard v-if="isVisible" type="info" data-testid="profile-fields-admin-support-banner">
88
<div class="profile-fields-admin-support-banner">
99
<div class="profile-fields-admin-support-banner__copy">
10-
<p><strong>{{ t('profile_fields', 'Help keep Profile Fields sustainable.') }}</strong></p>
10+
<p><strong>{{ t('profile_fields', 'Help sustain Profile Fields development.') }}</strong></p>
1111
<p>{{ t('profile_fields', 'Profile Fields is open source under the AGPL license and maintained by the LibreCode team, creators of LibreSign.') }}</p>
12-
<p>{{ t('profile_fields', 'If your organization depends on it, please help us sustain its development and maintenance.') }}</p>
12+
<p>{{ t('profile_fields', 'If your organization depends on this app, please help fund ongoing development and maintenance.') }}</p>
1313

1414
<div class="profile-fields-admin-support-banner__actions">
1515
<NcButton class="profile-fields-admin-support-banner__action" variant="primary" @click="openSponsorPage">
@@ -23,17 +23,17 @@ SPDX-License-Identifier: AGPL-3.0-or-later
2323

2424
<div class="profile-fields-admin-support-banner__links">
2525
<a href="https://github.com/LibreCodeCoop/profile_fields" target="_blank" rel="noopener noreferrer nofollow">
26-
{{ t('profile_fields', 'Give Profile Fields a {star} on GitHub', {star: '⭐'}) }}
26+
{{ githubStarCtaLabel }}
2727
</a>
28-
<a href="mailto:contact@librecode.coop">{{ t('profile_fields', 'Contact us for support or custom development') }}</a>
28+
<a href="mailto:contact@librecode.coop">{{ t('profile_fields', 'Contact us for support or custom development services') }}</a>
2929
</div>
3030
</div>
3131
</div>
3232
</NcNoteCard>
3333
</template>
3434

3535
<script setup lang="ts">
36-
import { onMounted, ref } from 'vue'
36+
import { computed, onMounted, ref } from 'vue'
3737
import { t } from '@nextcloud/l10n'
3838
import { NcButton, NcNoteCard } from '@nextcloud/vue'
3939
@@ -47,6 +47,9 @@ const props = withDefaults(defineProps<{
4747
4848
const isVisible = ref(true)
4949
50+
// TRANSLATORS "{star}" is replaced with a star symbol (for example: "⭐").
51+
const githubStarCtaLabel = computed(() => t('profile_fields', 'Star Profile Fields on GitHub {star}', { star: '' }))
52+
5053
const dismissBanner = () => {
5154
isVisible.value = false
5255
try {

src/components/AdminUserFieldsDialog.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
7575
/>
7676

7777
<div class="profile-fields-user-dialog__visibility-control" :class="{ 'profile-fields-user-dialog__visibility-control--error': fieldHasError(field) }">
78-
<label class="profile-fields-user-dialog__control-label" :for="`profile-fields-user-dialog-visibility-${field.definition.id}`">{{ t('profile_fields', 'Who can see this') }}</label>
78+
<label class="profile-fields-user-dialog__control-label" :for="`profile-fields-user-dialog-visibility-${field.definition.id}`">{{ visibilityFieldLabel }}</label>
7979
<NcSelect
8080
:input-id="`profile-fields-user-dialog-visibility-${field.definition.id}`"
8181
:model-value="visibilityOptionFor(field.definition.id)"
@@ -156,6 +156,7 @@ export default defineComponent({
156156
const userDraftVisibilities = reactive<Record<number, FieldVisibility>>({})
157157
158158
const headerUserName = computed(() => props.userDisplayName.trim() !== '' ? props.userDisplayName : props.userUid)
159+
const visibilityFieldLabel = t('profile_fields', 'Who can view this field value')
159160
const loadingMessage = computed(() => t('profile_fields', 'Loading profile fields for {userUid}...', { userUid: props.userUid }))
160161
const editableFields = computed<AdminEditableField[]>(() => buildAdminEditableFields(definitions.value, userValues.value))
161162
const isSavingAny = computed(() => savingIds.value.length > 0)
@@ -165,6 +166,7 @@ export default defineComponent({
165166
}
166167
167168
const count = editableFields.value.length
169+
// TRANSLATORS "{count}" is the number of editable fields and "{userUid}" is the account ID (without @).
168170
return n('profile_fields', '{count} editable field for @{userUid}.', '{count} editable fields for @{userUid}.', count, {
169171
count,
170172
userUid: props.userUid,
@@ -220,12 +222,14 @@ export default defineComponent({
220222
}
221223
222224
if (field.definition.type === 'number' && !plainNumberPattern.test(rawValue)) {
225+
// TRANSLATORS "{fieldLabel}" is a profile field label.
223226
return t('profile_fields', '{fieldLabel} must be a plain numeric value.', { fieldLabel: field.definition.label })
224227
}
225228
226229
if (field.definition.type === 'select') {
227230
const options = field.definition.options ?? []
228231
if (!options.includes(rawValue)) {
232+
// TRANSLATORS "{fieldLabel}" is a profile field label.
229233
return t('profile_fields', '{fieldLabel} must be one of the allowed options.', { fieldLabel: field.definition.label })
230234
}
231235
}
@@ -311,6 +315,7 @@ export default defineComponent({
311315
'current_visibility is not supported': t('profile_fields', 'The selected visibility is not supported.'),
312316
}[message] ?? (message.includes('is not a valid option')
313317
? t('profile_fields', '{fieldLabel}: invalid option selected.', { fieldLabel: field.definition.label })
318+
// TRANSLATORS "{fieldLabel}" is the field label and "{message}" is a backend error message.
314319
: t('profile_fields', '{fieldLabel}: {message}', { fieldLabel: field.definition.label, message })))
315320
}
316321
@@ -444,6 +449,7 @@ export default defineComponent({
444449
445450
const hasFieldErrors = changedFields.some((field: AdminEditableField) => Boolean(userValueErrors[field.definition.id]))
446451
if (!hasFieldErrors) {
452+
// TRANSLATORS "{userUid}" is the account ID whose fields were saved.
447453
successMessage.value = t('profile_fields', 'Saved profile fields for {userUid}.', { userUid: props.userUid })
448454
} else {
449455
errorMessage.value = n('profile_fields', 'The field could not be saved.', 'Some fields could not be saved. Review the messages below.', changedFields.length, {
@@ -475,6 +481,7 @@ export default defineComponent({
475481
headerDescription,
476482
headerUserName,
477483
loadingMessage,
484+
visibilityFieldLabel,
478485
hasPendingChanges,
479486
hasInvalidFields,
480487
helperTextForField,

src/tests/components/admin/AdminSupportBanner.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ describe("AdminSupportBanner", () => {
4242
const wrapper = mount(AdminSupportBanner);
4343

4444
expect(wrapper.text()).toContain(
45-
"tr:Help keep Profile Fields sustainable.",
45+
"tr:Help sustain Profile Fields development.",
4646
);
4747
expect(wrapper.text()).toContain(
4848
"tr:Profile Fields is open source under the AGPL license and maintained by the LibreCode team, creators of LibreSign.",
4949
);
5050
expect(wrapper.text()).toContain(
51-
"tr:If your organization depends on it, please help us sustain its development and maintenance.",
51+
"tr:If your organization depends on this app, please help fund ongoing development and maintenance.",
5252
);
5353
expect(wrapper.text()).toContain("tr:Sponsor LibreSign");
5454
expect(wrapper.text()).toContain("tr:Maybe later");
55-
expect(wrapper.text()).toContain("tr:Give Profile Fields a ⭐ on GitHub");
55+
expect(wrapper.text()).toContain("tr:Star Profile Fields on GitHub");
5656
expect(wrapper.text()).toContain(
57-
"tr:Contact us for support or custom development",
57+
"tr:Contact us for support or custom development services",
5858
);
5959
});
6060

0 commit comments

Comments
 (0)