Skip to content

Commit

Permalink
Walk json schema to translate field title/desc
Browse files Browse the repository at this point in the history
  • Loading branch information
arildm committed Jul 15, 2024
1 parent c2964e7 commit 5ca657b
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 55 deletions.
18 changes: 18 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
declare module "vue-matomo";

/** @see https://github.com/cloudflare/json-schema-tools/blob/master/workspaces/json-schema-walker/README.md */
declare module "@cloudflare/json-schema-walker" {
export type Visitor = (
schema: import("json-schema").JSONSchema6 | boolean,
path: string[],
parent?: import("json-schema").JSONSchema6,
parentPath?: string[],
) => void;

/** Modifies in place */
export function schemaWalk(
schema: import("json-schema").JSONSchema6,
preFunc?: Visitor,
postFunc?: Visitor,
vocabulary?: any,
): void;
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"type": "module",
"dependencies": {
"@cloudflare/json-schema-walker": "^0.1.1",
"@formkit/vue": "^1.0.0-beta.14",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
Expand All @@ -27,6 +28,7 @@
"@rjsf/core": "^5.18.4",
"@rjsf/utils": "^5.18.4",
"@rjsf/validator-ajv8": "^5.18.4",
"@types/json-schema": "^7.0.15",
"@vitejs/plugin-vue": "^5",
"@vue/tsconfig": "^0.5.1",
"@vueuse/core": "^10.7.2",
Expand Down
5 changes: 5 additions & 0 deletions src/corpus/config/SchemaConfig.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed } from "vue";
import Yaml from "js-yaml";
import type { JSONSchema6 } from "json-schema";
import { useTransformSchema } from "./schema-transform";
import schema from "@/assets/sparvconfig.schema.json";
import useCorpusIdParam from "@/corpus/corpusIdParam.composable";
import useConfig from "@/corpus/config/config.composable";
Expand All @@ -9,11 +11,14 @@ import type { SparvConfig } from "@/api/sparvConfig.types";
const corpusId = useCorpusIdParam();
const { config, uploadConfigRaw } = useConfig(corpusId);
const { transformSchema } = useTransformSchema();
const configParsed = computed(() =>
config.value ? (Yaml.load(config.value) as SparvConfig) : undefined,
);
transformSchema(schema as JSONSchema6);
async function onSubmit(event: { formData: SparvConfig }) {
const configYaml = Yaml.dump(event.formData);
await uploadConfigRaw(configYaml);
Expand Down
44 changes: 44 additions & 0 deletions src/corpus/config/schema-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { JSONSchema6 } from "json-schema";
import { schemaWalk, type Visitor } from "@cloudflare/json-schema-walker";
import { useI18n } from "vue-i18n";
import { capitalize } from "lodash";

const isPropertyName = (name: string) =>
!/^[0-9]*$/.test(name) &&
!["properties", "allOf", "anyOf", "if", "then", "else", "not"].includes(name);

export function useTransformSchema() {
const { t, te } = useI18n();

function transformSchema(schema: JSONSchema6) {
schemaWalk(schema, undefined, postFunc);
}

/** Callback for massaging each schema node. */
const postFunc: Visitor = (schema, path, parent, parentPath) => {
if (typeof schema != "object") return;
// For a property node, `path` will be `["properties", name]`
if (path[0] != "properties") return;
const name = path[1];

// Construct path as string
const pathStr = [...(parentPath || []), name]
.filter(isPropertyName)
.join(".");

// Build translation keys
const titleKey = `config.schema.${pathStr}.title`;
const descrKey = `config.schema.${pathStr}.description`;

// Set field title from translation, or fall back to prettified field name
schema.title = te(titleKey) ? t(titleKey) : prettyName(name);
// Set field description from translation, if any
if (te(descrKey)) schema.description = t(descrKey);
};

/** Convert "foo_bar_baz" to "Foo bar baz" */
const prettyName = (name: string): string =>
capitalize(name.replace(/_/g, " "));

return { transformSchema };
}
12 changes: 11 additions & 1 deletion src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createI18n } from "vue-i18n";
import en from "@/i18n/locales/en.yaml";
import sv from "@/i18n/locales/sv.yaml";

export default createI18n({
const i18n = createI18n({
legacy: false,
globalInjection: true,
// Prefer Swedish if it's among browser's preferred languages, even if English is ranked higher
Expand All @@ -13,10 +13,20 @@ export default createI18n({
messages: { en, sv },
});

export default i18n;

export type LocaleId = "sv" | "en";

/** Each UI language name, written in that language, keyed by its 2-letter locale id. */
export const languageNames = {
sv: "Svenska",
en: "English",
};

// Thanks @jclaveau: https://github.com/kazupon/vue-i18n/issues/1521#issuecomment-1799047164
i18n.global.te = (key, locale?): boolean => {

Check failure on line 27 in src/i18n/i18n.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Type instantiation is excessively deep and possibly infinite.

Check failure on line 27 in src/i18n/i18n.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Type instantiation is excessively deep and possibly infinite.

Check failure on line 27 in src/i18n/i18n.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Type instantiation is excessively deep and possibly infinite.
const effectiveLocale =
locale && locale.length ? locale : i18n.global.locale.value;
const messages = i18n.global.messages.value[effectiveLocale];
return Object.prototype.hasOwnProperty.call(messages, key);
};
36 changes: 20 additions & 16 deletions src/i18n/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,22 +199,26 @@ config.format.help: Select the file format of the source texts. All files must b
config.format.note.pdf: The PDF file format is primarily designed for display. Please note that extracting text from them can sometimes give unsatisfactory results. Performing OCR on scanned documents is (currently) outside the scope of Mink.
config.text_annotation: Text element
config.text_annotation.help: In your source files, what XML element is most representative of a single text? Any text-level annotations will be attached to this element.
config.schema.classes: Classes
config.schema.parent: Parent
config.schema.threads: Threads
config.schema.export: Export
config.schema.word: Word
config.schema.remove_module_namespaces: Remove module namespaces
config.schema.sparv_namespace: Sparv namespace
config.schema.source_namespace: Source namespace
config.schema.scramble_on: Scramble on
config.schema.import: Import
config.schema.text_annotation: Text annotation
config.schema.source_dir: Source dir
config.schema.importer: Importer
config.schema.keep_control_chars: Keep control chars
config.schema.normalize: Normalize
config.schema.encoding: Encoding

config.schema.classes.title: Classes
config.schema.parent.title: Parent
config.schema.threads.title: Threads
config.schema.export.title: Export
config.schema.export.word.title: Word
config.schema.export.remove_module_namespaces.title: Remove module namespaces
config.schema.export.sparv_namespace.title: Sparv namespace
config.schema.export.source_namespace.title: Source namespace
config.schema.export.scramble_on.title: Scramble on
config.schema.import.title: Import
config.schema.import.text_annotation.title: Text annotation
config.schema.import.source_dir.title: Source dir
config.schema.import.importer.title: Importer
config.schema.import.keep_control_chars.title: Keep control chars
config.schema.import.normalize.title: Normalize
config.schema.import.encoding.title: Encoding
config.schema.conll_export.title: CoNLL export
# TODO: Add more

segmenter_sentence: Existing sentence segmentation
segmenter_sentence_help: Are the text files already formatted such that sentences are separated by linebreaks?
segmenter_linebreaks: One per line
Expand Down
59 changes: 32 additions & 27 deletions src/i18n/locales/sv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,33 +200,38 @@ config.format.help: Ange vilket filformat källtexterna har. Alla filer måste h
config.format.note.pdf: PDF-formatet är byggt främst för visuell presentation. Text som extraheras från PDF-filer kan ibland ge bristfälliga resultat. OCR-behandling av scannade dokument är (för närvarande) inget som Mink stödjer.
config.text_annotation: Textelement
config.text_annotation.help: Vilket XML-element i dina källfiler representerar bäst en enskild text? Eventuella annotationer på textnivå kommer att sättas på det elementet.
config.schema.classes: Klasser
config.schema.parent: Förälder
config.schema.threads: Trådar
config.schema.export: Export
config.schema.word: Ord
config.schema.word.description: Annotering att använda för tokentext i exporten
config.schema.remove_module_namespaces: Ta bort modulnamnrymder
config.schema.remove_module_namespaces.description: Ta bort modulnamnsprefix från annoteringsnamn i exporten
config.schema.sparv_namespace: Namnrymd för Sparv
config.schema.sparv_namespace.description: Prefix att lägga till på namnen för alla automatiskt skapade annoteringar
config.schema.source_namespace: Namnrymd för källa
config.schema.source_namespace.description: Prefix att lägga till på namnen för alla annoteringar från källan
config.schema.scramble_on: Nivå för omkastning
config.schema.scramble_on.description: Vilken annotering som används som minsta element vid omkastning
config.schema.import: Import
config.schema.text_annotation: Textannotering
config.schema.text_annotation.description: Annotering som representerar en text
config.schema.source_dir: Källkatalog
config.schema.source_dir.description: Katalog som innehåller korpuskällfiler
config.schema.importer: Importerare
config.schema.importer.description: Namnet på importerare att använda
config.schema.keep_control_chars: Behåll kontrolltecken
config.schema.keep_control_chars.description: Slå på för att behålla kontrolltecken
config.schema.normalize: Normalisera
config.schema.normalize.description: "Normalisera inmatning med någon av följande former: 'NFC', 'NFKC', 'NFD' eller 'NFKD'"
config.schema.encoding: Teckenkodning
config.schema.encoding.description: Teckenkodning för källfiler

config.schema.classes.title: Klasser
config.schema.parent.title: Förälder
config.schema.threads.title: Trådar
config.schema.export.title: Export
config.schema.export.word.title: Ord
config.schema.export.word.description: Annotering att använda för tokentext i exporten
config.schema.export.remove_module_namespaces.title: Ta bort modulnamnrymder
config.schema.export.remove_module_namespaces.description: Ta bort modulnamnsprefix från annoteringsnamn i exporten
config.schema.export.sparv_namespace.title: Namnrymd för Sparv
config.schema.export.sparv_namespace.description: Prefix att lägga till på namnen för alla automatiskt skapade annoteringar
config.schema.export.source_namespace.title: Namnrymd för källa
config.schema.export.source_namespace.description: Prefix att lägga till på namnen för alla annoteringar från källan
config.schema.export.scramble_on.title: Nivå för omkastning
config.schema.export.scramble_on.description: Vilken annotering som används som minsta element vid omkastning
config.schema.import.title: Import
config.schema.import.text_annotation.title: Textannotering
config.schema.import.text_annotation.description: Annotering som representerar en text
config.schema.import.source_dir.title: Källkatalog
config.schema.import.source_dir.description: Katalog som innehåller korpuskällfiler
config.schema.import.importer.title: Importerare
config.schema.import.importer.description: Namnet på importerare att använda
config.schema.import.keep_control_chars.title: Behåll kontrolltecken
config.schema.import.keep_control_chars.description: Slå på för att behålla kontrolltecken
config.schema.import.normalize.title: Normalisera
config.schema.import.normalize.description: "Normalisera inmatning med någon av följande former: 'NFC', 'NFKC', 'NFD' eller 'NFKD'"
config.schema.import.encoding.title: Teckenkodning
config.schema.import.encoding.description: Teckenkodning för källfiler
config.schema.conll_export.title: CoNLL-export
config.schema.conll_export.description: Export till CoNLL-fil
# TODO: Add more

segmenter_sentence: Existerande meningssegmentering
segmenter_sentence_help: Är källtexterna redan formaterade så att varje mening börjar på en ny rad?
segmenter_linebreaks: En per rad
Expand Down
12 changes: 2 additions & 10 deletions src/message/messenger.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type MessageLevel = "error" | "success" | "debug";
const alerts = ref<Alert[]>([]);

export default function useMessenger() {
const { t, locale, messages } = useI18n();
const { t, te, locale, messages } = useI18n();

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'locale' is assigned a value but never used

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'messages' is assigned a value but never used

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

'locale' is assigned a value but never used

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

'messages' is assigned a value but never used

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

'locale' is assigned a value but never used

Check warning on line 19 in src/message/messenger.composable.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

'messages' is assigned a value but never used

function alert(message: string, level?: MessageLevel) {
if (message && level !== "success") {
Expand All @@ -37,14 +37,6 @@ export default function useMessenger() {
alerts.value = [];
}

/** Check if there is a translation for the given key.
*
* This replaces `te` until https://github.com/kazupon/vue-i18n/issues/1521 is fixed.
*/
function translationExists(key: string) {
return !!messages.value[locale.value][key];
}

/** Display a backend error message. */
const alertError = (err: AxiosError<MinkResponse>): undefined => {
if (err.response?.data) {
Expand All @@ -53,7 +45,7 @@ export default function useMessenger() {
// Use the return code to find a message, if available.
if (data.return_code) {
const translationKey = `api.code.${data.return_code}`;
if (translationExists(translationKey)) {
if (te(translationKey)) {
// Pass the response data, plus request params, as variables to be replaced for "{placeholders}" in the translation message.
const messageVariables = { ...err.config?.params, ...data };
alert(t(translationKey, messageVariables), "error");
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
dependencies:
regenerator-runtime "^0.14.0"

"@cloudflare/json-schema-walker@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@cloudflare/json-schema-walker/-/json-schema-walker-0.1.1.tgz#d1cc94065327b0b3800158db40cb78124a3476de"
integrity sha512-P3n0hEgk1m6uKWgL4Yb1owzXVG4pM70G4kRnDQxZXiVvfCRtaqiHu+ZRiRPzmwGBiLTO1LWc2yR1M8oz0YkXww==

"@esbuild/aix-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
Expand Down Expand Up @@ -594,7 +599,7 @@
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==

"@types/json-schema@^7.0.12":
"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
Expand Down

0 comments on commit 5ca657b

Please sign in to comment.