Skip to content
Open
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
3 changes: 1 addition & 2 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ plugins:
- "@typescript-eslint"
- simple-import-sort
rules:
"@typescript-eslint/no-unused-vars":
- error
"@typescript-eslint/no-unused-vars": "error"
react/prop-types:
- off
prettier/prettier:
Expand Down
9 changes: 4 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Builder container
FROM registry.ci.openshift.org/ocp/builder:rhel-8-base-nodejs-openshift-4.15 AS build

# Install yarn
RUN npm install -g yarn -s &>/dev/null
FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-nodejs-openshift-4.22 AS build

# Copy app source
COPY . /opt/app-root/src/app
WORKDIR /opt/app-root/src/app

# Run install as supper tux
USER 0
RUN yarn install --frozen-lockfile --network-timeout 600000 && yarn build
RUN npm ci && \
npm install --no-save @esbuild/linux-x64 && \
npm run build

# Web server container
FROM registry.access.redhat.com/ubi9/nginx-120
Expand Down
16 changes: 1 addition & 15 deletions Dockerfile.art
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,8 @@ COPY . /opt/app-root/src/app
COPY $REMOTE_SOURCES $REMOTE_SOURCES_DIR
WORKDIR /opt/app-root/src/app

# bootstrap yarn so we can install and run the other tools.
USER 0
ARG YARN_VERSION=v1.22.19
RUN CACHED_YARN=./artifacts/yarn-${YARN_VERSION}.tar.gz; \
if [ -f ${CACHED_YARN} ]; then \
npm install -g ${CACHED_YARN}; \
else \
echo "need yarn at ${CACHED_YARN}"; \
exit 1; \
fi

# use dependencies provided by Cachito
RUN test -d ${REMOTE_SOURCES_DIR}/cachito-gomod-with-deps || exit 1; \
cp -f $REMOTE_SOURCES_DIR/cachito-gomod-with-deps/app/{.npmrc,.yarnrc,yarn.lock,registry-ca.pem} . \
&& source ${REMOTE_SOURCES_DIR}/cachito-gomod-with-deps/cachito.env \
&& yarn install --frozen-lockfile && yarn build
RUN npm ci && npm run build

# Web server container
FROM registry.ci.openshift.org/ocp/4.20:base-rhel9
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ cd nmstate-console-plugin
# edit the code

# start a develoment server on port 9443
yarn dev --port 9443 \
npm run dev -- --port 9443 \
--server-type https \
--server-options-key /var/serving-cert/tls.key \
--server-options-cert /var/serving-cert/tls.crt
Expand All @@ -36,7 +36,7 @@ nmstate-console-plugin development require web development tools, and a kubernet
| requirements | |
| -------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [nodejs](https://nodejs.org/) | JavaScript runtime environment |
| [yarn](https://yarnpkg.com/) | package manager for nodejs |
| [npm](https://www.npmjs.com/) | package manager for nodejs (comes with nodejs) |
| [kubernetes]() | An [Openshift](<(https://www.openshift.com/)>) or kubernetes cluster for development |
| [kubectl](https://kubernetes.io/docs/tasks/tools/) | The Kubernetes command-line tool |

Expand Down Expand Up @@ -65,8 +65,8 @@ See [cli docs](https://github.com/kubev2v/forklift-console-plugin/blob/main/docs

In one terminal window, run:

1. `yarn install`
1. `yarn run start-console`
1. `npm install`
1. `npm run start-console`

This will run the OpenShift console in a container connected to the cluster you are currently logged into. The plugin HTTP server runs on port 9001 with CORS enabled, the development server will be available at http://localhost:9000

Expand Down Expand Up @@ -101,7 +101,7 @@ export BRIDGE_K8S_AUTH_BEARER_TOKEN=$(oc whoami --show-token)
export INVENTORY_SERVER_HOST=https://$(oc get routes -o custom-columns=HOST:.spec.host -A | grep 'nmstate-inventory' | head -n 1)

# start the nmstate console plugin
yarn dev
npm run dev
```

### Deployment on cluster with Openshift templates
Expand Down
7 changes: 7 additions & 0 deletions i18n-scripts/build-i18n.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -exuo pipefail

FILE_PATTERN="{!(dist|node_modules)/**/*.{js,jsx,ts,tsx,json},*.{js,jsx,ts,tsx,json}}"

i18next "${FILE_PATTERN}" [-oc] -c "./i18next-parser.config.mjs" -o "locales/\$LOCALE/\$NAMESPACE.json"
34 changes: 34 additions & 0 deletions i18n-scripts/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const fs = require('fs');
const path = require('path');

module.exports = {
isDirectory(filePath) {
try {
const stat = fs.lstatSync(filePath);
return stat.isDirectory();
} catch (e) {
// lstatSync throws an error if path doesn't exist
return false;
}
},
parseFolder(directory, argFunction, packageDir) {
(async () => {
try {
const files = await fs.promises.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
argFunction(filePath, packageDir);
}
} catch (e) {
console.error(`Failed to parseFolder ${directory}:`, e);
}
})();
},
deleteFile(filePath) {
try {
fs.unlinkSync(filePath);
} catch (e) {
console.error(`Failed to delete file ${filePath}:`, e);
}
},
};
13 changes: 13 additions & 0 deletions i18n-scripts/export-pos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -exuo pipefail

source ./i18n-scripts/languages.sh

for f in locales/en/* ; do
for i in "${LANGUAGES[@]}"
do
npm run i18n-to-po -- -f "$(basename "$f" .json)" -l "$i"
done
done

117 changes: 117 additions & 0 deletions i18n-scripts/i18n-to-po.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const common = require('./common.js');

function save(target) {
return (result) => {
fs.writeFileSync(target, result);
};
}

function removeValues(i18nFile, filePath) {
const file = require(i18nFile);

const updatedFile = {};

const keys = Object.keys(file);

for (let i = 0; i < keys.length; i++) {
updatedFile[keys[i]] = '';
}

const tmpFile = fs.openSync(filePath, 'w');

fs.writeFileSync(tmpFile, JSON.stringify(updatedFile, null, 2));
}

function consolidateWithExistingTranslations(filePath, fileName, language) {
const englishFile = require(filePath);
const englishKeys = Object.keys(englishFile);
const existingTranslationsPath = `./../locales/${language}/${fileName}.json`;
if (fs.existsSync(path.join(__dirname, existingTranslationsPath))) {
const existingTranslationsFile = require(path.join(__dirname, existingTranslationsPath));
const existingKeys = Object.keys(existingTranslationsFile);
const matchingKeys = englishKeys.filter((k) => existingKeys.indexOf(k) > -1);

for (let i = 0; i < matchingKeys.length; i++) {
englishFile[matchingKeys[i]] = existingTranslationsFile[matchingKeys[i]];
}

fs.writeFileSync(filePath, JSON.stringify(englishFile, null, 2));
}
}

function processFile(fileName, language, i18nextToPo) {
let tmpFile;

const i18nFile = path.join(__dirname, `./../locales/en/${fileName}.json`);

try {
if (fs.existsSync(i18nFile)) {
fs.mkdirSync(path.join(__dirname, './../locales/tmp'), { recursive: true });

tmpFile = path.join(__dirname, `./../locales/tmp/${fileName}.json`);

removeValues(i18nFile, tmpFile);
consolidateWithExistingTranslations(tmpFile, fileName, language);

fs.mkdirSync(path.join(__dirname, `./../po-files/${language}`), { recursive: true });
i18nextToPo(language, fs.readFileSync(tmpFile), {
language,
foldLength: 0,
ctxSeparator: '~',
})
.then(
save(
path.join(
__dirname,
`./../po-files/${language}/${path.basename(fileName)}.po`,
),
language,
),
)
.catch((e) => console.error(fileName, e));
}
} catch (err) {
console.error(`Failed to processFile ${fileName}:`, err);
}

common.deleteFile(tmpFile);
console.log(`Processed ${fileName}`);
}

const options = {
string: ['language', 'file'],
boolean: ['help'],
array: ['files'],
alias: {
h: 'help',
f: 'files',
l: 'language',
},
default: {
files: [],
},
};

const args = minimist(process.argv.slice(2), options);

async function main() {
const { i18nextToPo } = await import('i18next-conv');
if (args.help) {
console.log(
"-h: help\n-l: language (i.e. 'ja')\n-f: file name to convert (i.e. 'plugin__kubevirt-plugin')",
);
} else if (args.files && args.language) {
if (Array.isArray(args.files)) {
for (let i = 0; i < args.files.length; i++) {
processFile(args.files[i], args.language, i18nextToPo);
}
} else {
processFile(args.files, args.language, i18nextToPo);
}
}
}

main().catch((e) => console.error(e));
3 changes: 3 additions & 0 deletions i18n-scripts/languages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

export LANGUAGES=( 'ja' 'zh-cn' 'ko' 'fr' 'es')
31 changes: 31 additions & 0 deletions i18n-scripts/lexers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const EventEmitter = require('events');
const jsonc = require('comment-json');

/**
* Custom JSON parser for localizing keys matching format: /%.+%/
*/
module.exports.CustomJSONLexer = class extends EventEmitter {
extract(content, filename) {
let keys = [];
console.log(1)
try {
jsonc.parse(
content,
(key, value) => {
if (typeof value === 'string') {
const match = value.match(/^%(.+)%$/);
if (match && match[1]) {
keys.push({ key: match[1] });
}
}
return value;
},
true,
);
} catch (e) {
console.error('Failed to parse as JSON.', filename, e);
keys = [];
}
return keys;
}
};
72 changes: 72 additions & 0 deletions i18n-scripts/po-to-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');

function save(target) {
return (result) => {
fs.writeFileSync(target, JSON.stringify(JSON.parse(result), null, 2));
};
}

function processFile(fileName, language, gettextToI18next) {
if (fileName.includes('.DS_Store')) {
return;
}
const newFileName = path.basename(fileName, '.po');

if (!fs.existsSync(path.join(__dirname, `../locales/${language}/`))) {
fs.mkdirSync(path.join(__dirname, `../locales/${language}/`), { recursive: true });
}
const newFilePath = path.join(
__dirname,
`../locales/${language}/plugin__nmstate-console-plugin.json`,
);
console.log(`Saving locales/${language}/${newFileName}.json`);

gettextToI18next(language, fs.readFileSync(fileName))
.then(save(newFilePath))
.catch((e) => console.error(fileName, e));
}

function processDirectory(directory, language, gettextToI18next) {
if (fs.existsSync(directory)) {
(async () => {
try {
const files = await fs.promises.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
processFile(filePath, language, gettextToI18next);
}
} catch (e) {
console.error(`Failed to processDirectory ${directory}:`, e);
}
})();
} else {
console.error('Directory does not exist.');
}
}

const options = {
string: ['language', 'directory'],
boolean: ['help'],
alias: {
h: 'help',
d: 'directory',
l: 'language',
},
};

const args = minimist(process.argv.slice(2), options);

async function main() {
const { gettextToI18next } = await import('i18next-conv');
if (args.help) {
console.log(
"-h: help\n-l: language (i.e. 'ja')\n-d: directory to convert files in (i.e. './new-pos')",
);
} else if (args.directory && args.language) {
processDirectory(args.directory, args.language, gettextToI18next);
}
}

main().catch((e) => console.error(e));
Loading