Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve translation handling #701

Merged
merged 1 commit into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 2 additions & 7 deletions .github/workflows/weblate-update-pot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,10 @@ jobs:
path: agama

- name: Generate POT file
# TODO: use a shared script for this
run: |
cd agama/web
xgettext --default-domain=agama --output=agama.pot --language=JavaScript --keyword= \
--keyword=_:1 --keyword=N_:1 --keyword=n_:1,2,3t --keyword=Nn_:1,2,3t \
--foreign-user --copyright-holder="SuSE Linux Products GmbH, Nuernberg" \
--from-code=UTF-8 --add-comments=TRANSLATORS --sort-by-file \
$(find . ! -name cockpit.js -name '*.js' -o ! -name '*.test.jsx' -name '*.jsx')
msgfmt --statistics agama.pot
./build_pot
msgfmt --statistics agama.pot
- name: Validate the generated POT file
run: msgfmt --check-format agama/web/agama.pot
Expand Down
1 change: 1 addition & 0 deletions web/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
}],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
"no-var": "error",
"no-multi-str": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "ignoreRestSiblings": true }],
"@typescript-eslint/no-use-before-define": "warn",
Expand Down
17 changes: 17 additions & 0 deletions web/build_pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#! /bin/sh

# This script builds the POT file with the translatable texts from the web UI.

# always run in the directory of this script to ensure the file paths
# saved in the POT file are always the same
cd "$(dirname "$0")"

OUTPUT="${1:-agama.pot}"

find . ! -name cockpit.js ! -name '*.test.js' -name '*.js' -o ! -name '*.test.jsx' -name '*.jsx' |
grep -vE "node_modules/|dist/|coverage/" | \
xgettext --default-domain=agama --output=- --language=C --files-from=- \
--keyword= --keyword=_:1 --keyword=N_:1 --keyword=n_:1,2,3t --keyword=Nn_:1,2,3t \
--foreign-user --copyright-holder="SuSE Linux Products GmbH, Nuernberg" \
--from-code=UTF-8 --add-comments=TRANSLATORS --sort-by-file | \
sed '/^#/ s/, c-format/, javascript-format/' > "$OUTPUT"
11 changes: 11 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"@patternfly/react-table": "^4.113.0",
"core-js": "^3.21.1",
"fast-sort": "^3.2.1",
"format-util": "^1.0.5",
"ipaddr.js": "^2.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
20 changes: 10 additions & 10 deletions web/src/components/core/About.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@

import React, { useState } from "react";
import { Button, Text } from "@patternfly/react-core";
import format from "format-util";

import { Icon } from "~/components/layout";
import { Popup } from "~/components/core";
import { _ } from "~/i18n";

import cockpit from "~/lib/cockpit";

export default function About() {
const [isOpen, setIsOpen] = useState(false);

Expand All @@ -50,19 +50,19 @@ export default function About() {
<Text>
{
// TRANSLATORS: content of the "About" popup (1/2)
_(`Agama is an experimental installer for (open)SUSE systems. It
still under development so, please, do not use it in
production environments. If you want to give it a try, we
recommend to use a virtual machine to prevent any possible
data loss.`)
_("Agama is an experimental installer for (open)SUSE systems. It \
still under development so, please, do not use it in \
production environments. If you want to give it a try, we \
recommend to use a virtual machine to prevent any possible \
data loss.")
}
</Text>
<Text>
{
cockpit.format(
format(
// TRANSLATORS: content of the "About" popup (2/2)
// $0 is replaced by the project URL
_("For more information, please visit the project's repository at $0."),
// %s is replaced by the project URL
_("For more information, please visit the project's repository at %s."),
"https://github.com/openSUSE/agama"
)
}
Expand Down
9 changes: 4 additions & 5 deletions web/src/components/core/ValidationErrors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ import {
ListItem,
Popover
} from "@patternfly/react-core";
import format from "format-util";

import { Icon } from '~/components/layout';
import { _, n_ } from "~/i18n";

import cockpit from "~/lib/cockpit";

/**
* @param {import("~/client/mixins").ValidationError[]} errors - Validation errors
* @return React.JSX
Expand Down Expand Up @@ -82,9 +81,9 @@ const ValidationErrors = ({ title = _("Errors"), errors }) => {
onClick={() => setPopoverVisible(true)}
>
{ warningIcon } {
cockpit.format(
// TRANSLATORS: $0 is replaced with the number of errors found
n_("$0 error found", "$0 errors found", errors.length),
format(
// TRANSLATORS: %d is replaced with the number of errors found
n_("%d error found", "%d errors found", errors.length),
errors.length
)
}
Expand Down
16 changes: 8 additions & 8 deletions web/src/components/network/ConnectionsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@

import React from "react";
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import format from "format-util";

import { RowActions } from "~/components/core";
import { Icon } from "~/components/layout";
import { formatIp } from "~/client/network/utils";
import { _ } from "~/i18n";

import cockpit from "~/lib/cockpit";

/**
* @typedef {import("~/client/network/model").Connection} Connection
*/
Expand Down Expand Up @@ -66,15 +66,15 @@ export default function ConnectionsTable ({
{
title: _("Edit"),
"aria-label":
// TRANSLATORS: $0 is replaced by a network connection name
cockpit.format(_("Edit connection $0"), connection.name),
// TRANSLATORS: %s is replaced by a network connection name
format(_("Edit connection %s"), connection.name),
onClick: () => onEdit(connection)
},
typeof onForget === 'function' && {
title: _("Forget"),
"aria-label":
// TRANSLATORS: $0 is replaced by a network connection name
cockpit.format(_("Forget connection $0"), connection.name),
// TRANSLATORS: %s is replaced by a network connection name
format(_("Forget connection %s"), connection.name),
className: "danger-action",
icon: <Icon name="delete" size="24" />,
onClick: () => onForget(connection)
Expand All @@ -88,8 +88,8 @@ export default function ConnectionsTable ({
<Td isActionCell>
<RowActions
id={`actions-for-connection-${connection.id}`}
// TRANSLATORS: $0 is replaced by a network connection name
aria-label={cockpit.format(_("Actions for connection $0"), connection.name)}
// TRANSLATORS: %s is replaced by a network connection name
aria-label={format(_("Actions for connection %s"), connection.name)}
actions={actions}
connection={connection}
/>
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/network/IpSettingsForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@

import React, { useState } from "react";
import { HelperText, HelperTextItem, Form, FormGroup, FormSelect, FormSelectOption, TextInput } from "@patternfly/react-core";
import format from "format-util";

import { useInstallerClient } from "~/context/installer";
import { Popup } from "~/components/core";
import { AddressesDataList, DnsDataList } from "~/components/network";
import { _ } from "~/i18n";

import cockpit from "~/lib/cockpit";

const METHODS = {
MANUAL: "manual",
AUTO: "auto"
Expand Down Expand Up @@ -127,8 +126,9 @@ export default function IpSettingsForm({ connection, onClose }) {
};

// TRANSLATORS: manual network configuration mode with a static IP address
// %s is replaced by the connection name
return (
<Popup isOpen title={cockpit.format(_("Edit $0 connection"), connection.name)}>
<Popup isOpen title={format(_("Edit %s connection"), connection.name)}>
<Form id="edit-connection" onSubmit={onSubmit}>
<FormGroup fieldId="method" label={_("Mode")} isRequired>
<FormSelect
Expand Down
3 changes: 1 addition & 2 deletions web/src/components/network/WifiConnectionForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ export default function WifiConnectionForm({ network, onCancel, onSubmitCallback
<FormGroup fieldId="security" label={_("Security")}>
<FormSelect
id="security"
// TRANSLATORS: Wifi security configuration (password protected or not)
aria-label={_("security")}
aria-label={_("Security")}
value={security}
onChange={setSecurity}
>
Expand Down
7 changes: 3 additions & 4 deletions web/src/components/network/WifiNetworkListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ import {
Spinner,
Text
} from "@patternfly/react-core";
import format from "format-util";

import { ConnectionState } from "~/client/network/model";

import { Icon } from "~/components/layout";
import { WifiNetworkMenu, WifiConnectionForm } from "~/components/network";
import { _ } from "~/i18n";

import cockpit from "~/lib/cockpit";

const networkState = (state) => {
switch (state) {
case ConnectionState.ACTIVATING:
Expand Down Expand Up @@ -90,8 +89,8 @@ function WifiNetworkListItem ({ network, isSelected, isActive, onSelect, onCance
onClick={onSelect}
/>
<div className="split">
{/* TRANSLATORS: $0 is replaced by a WiFi network name */}
{showSpinner && <Spinner isSVG size="md" aria-label={cockpit.format(_("$0 connection is waiting for an state change"), network.ssid)} /> }
{/* TRANSLATORS: %s is replaced by a WiFi network name */}
{showSpinner && <Spinner isSVG size="md" aria-label={format(_("%s connection is waiting for an state change"), network.ssid)} /> }
<Text component="small" className="keep-words">
{ showSpinner && !network.connection && _("Connecting") }
{ networkState(network.connection?.state)}
Expand Down
Loading