diff --git a/Makefile b/Makefile index d47586306d0..f54f4f9415d 100755 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ check: checkstatic unittests testdata: php tools/raisinbread_refresh.php -locales: +locales: msgfmt -o locale/en/LC_MESSAGES/loris.mo locale/en/LC_MESSAGES/loris.po npx i18next-conv -l en -s locale/en/LC_MESSAGES/loris.po -t locale/en/LC_MESSAGES/loris.json --compatibilityJSON v4 msgfmt -o locale/fr/LC_MESSAGES/loris.mo locale/fr/LC_MESSAGES/loris.po @@ -120,6 +120,9 @@ locales: npx i18next-conv -l hi -s modules/datadict/locale/hi/LC_MESSAGES/datadict.po -t modules/datadict/locale/hi/LC_MESSAGES/datadict.json npx i18next-conv -l ja -s modules/datadict/locale/ja/LC_MESSAGES/datadict.po -t modules/datadict/locale/ja/LC_MESSAGES/datadict.json msgfmt -o modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo modules/dataquery/locale/ja/LC_MESSAGES/dataquery.po + msgfmt -o modules/dataquery/locale/hi/LC_MESSAGES/dataquery.mo modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po + npx i18next-conv -l ja -s modules/dataquery/locale/ja/LC_MESSAGES/dataquery.po -t modules/dataquery/locale/ja/LC_MESSAGES/dataquery.json + npx i18next-conv -l hi -s modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po -t modules/dataquery/locale/hi/LC_MESSAGES/dataquery.json msgfmt -o modules/data_release/locale/ja/LC_MESSAGES/data_release.mo modules/data_release/locale/ja/LC_MESSAGES/data_release.po npx i18next-conv -l ja -s modules/data_release/locale/ja/LC_MESSAGES/data_release.po -t modules/data_release/locale/ja/LC_MESSAGES/data_release.json msgfmt -o modules/data_release/locale/hi/LC_MESSAGES/data_release.mo modules/data_release/locale/hi/LC_MESSAGES/data_release.po @@ -224,14 +227,17 @@ data_release: modules/data_release/locale/hi/LC_MESSAGES/data_release.mo modules instrument_manager: modules/instrument_manager/locale/ja/LC_MESSAGES/instrument_manager.mo target=instrument_manager npm run compile +dataquery: modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo + msgfmt -o modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo modules/dataquery/locale/ja/LC_MESSAGES/dataquery.po + msgfmt -o modules/dataquery/locale/hi/LC_MESSAGES/dataquery.mo modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po + npx i18next-conv -l ja -s modules/dataquery/locale/ja/LC_MESSAGES/dataquery.po -t modules/dataquery/locale/ja/LC_MESSAGES/dataquery.json + npx i18next-conv -l hi -s modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po -t modules/dataquery/locale/hi/LC_MESSAGES/dataquery.json + target=dataquery npm run compile + instrument_builder: modules/instrument_builder/locale/ja/LC_MESSAGES/instrument_builder.mo modules/instrument_builder/locale/hi/LC_MESSAGES/instrument_builder.mo npx i18next-conv -l hi -s modules/instrument_builder/locale/hi/LC_MESSAGES/instrument_builder.po -t modules/instrument_builder/locale/hi/LC_MESSAGES/instrument_builder.json --compatibilityJSON v4 target=instrument_builder npm run compile -dataquery: modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo - msgfmt -o modules/dataquery/locale/ja/LC_MESSAGES/dataquery.mo modules/dataquery/locale/ja/LC_MESSAGES/dataquery.po - target=dataquery npm run compile - login: modules/login/locale/ja/LC_MESSAGES/login.mo npx i18next-conv -l ja -s modules/login/locale/ja/LC_MESSAGES/login.po -t modules/login/locale/ja/LC_MESSAGES/login.json --compatibilityJSON v4 target=login npm run compile diff --git a/jsx/Modal.tsx b/jsx/Modal.tsx index e81abacf040..bf9a86d4e01 100644 --- a/jsx/Modal.tsx +++ b/jsx/Modal.tsx @@ -47,14 +47,14 @@ const Modal = ({ const handleClose = () => { if (throwWarning) { // Display warning if enabled Swal.fire({ - title: t('Are You Sure?'), - text: - t('Leaving the form will result in the' - +' loss of any information entered.'), + title: t('Are You Sure?', {ns: 'loris'}), + text: t('Leaving the form will result in the loss ' + +'of any information entered.', + {ns: 'loris'}), type: 'warning', showCancelButton: true, - confirmButtonText: t('Proceed'), - cancelButtonText: t('Cancel'), + confirmButtonText: t('Proceed', {ns: 'loris'}), + cancelButtonText: t('Cancel', {ns: 'loris'}), }).then((result) => result.value && onClose()); } else { onClose(); // Close immediately if no warning @@ -178,7 +178,7 @@ const Modal = ({ const loader = loading && (
-
{t('Saving')}
+
{t('Saving', {ns: 'loris'})}
); @@ -191,7 +191,7 @@ const Modal = ({ style={{color: 'green', marginBottom: '2px'}} className='glyphicon glyphicon-ok-circle' /> -
{t('Success!')}
+
{t('Success!', {ns: 'loris'})}
); diff --git a/locale/en/LC_MESSAGES/loris.po b/locale/en/LC_MESSAGES/loris.po index 02f9fc747bc..6c5a55d09ff 100644 --- a/locale/en/LC_MESSAGES/loris.po +++ b/locale/en/LC_MESSAGES/loris.po @@ -124,6 +124,15 @@ msgstr "No" msgid "Cancel" msgstr "Cancel" +msgid "Clear" +msgstr "Clear" + +msgid "Submit" +msgstr "Submit" + +msgid "Proceed" +msgstr "Proceed" + # Common candidate terms msgid "PSCID" msgstr "PSCID" @@ -147,7 +156,7 @@ msgid "Cohort" msgid_plural "Cohorts" msgstr[0] "Cohort" msgstr[1] "Cohorts" - + msgid "Session" msgstr "Session" diff --git a/locale/hi/LC_MESSAGES/loris.po b/locale/hi/LC_MESSAGES/loris.po index 8326e30719e..c3ecc58200c 100644 --- a/locale/hi/LC_MESSAGES/loris.po +++ b/locale/hi/LC_MESSAGES/loris.po @@ -92,7 +92,9 @@ msgstr "इमेजिंग" #: modules/candidate_list/php/module.class.inc:76 #: modules/new_profile/php/module.class.inc:50 msgid "Candidate" -msgstr "उम्मीदवार" +msgid_plural "Candidates" +msgstr[0] "उम्मीदवार" +msgstr[1] "उम्मीदवारों" #: modules/genomic_browser/php/module.class.inc:53 msgid "Genomics" @@ -206,12 +208,27 @@ msgstr "चयनित" msgid "Cancel" msgstr "रद्द करें" +msgid "Clear" +msgstr "हटाना" + +msgid "Proceed" +msgstr "आगे बढ़ना" + msgid "Success!" msgstr "सफलता!" msgid "Close" msgstr "बंद करें" +msgid "Saving" +msgstr "सहेजा जा रहा है" + +msgid "Are You Sure?" +msgstr "क्या आप सुनिश्चित हैं?" + +msgid "Leaving the form will result in the loss of any information entered." +msgstr "फॉर्म छोड़ने पर दर्ज की गई सभी जानकारी खो जाएगी।" + # Common candidate terms msgid "PSCID" msgstr "पीएससीआईडी" @@ -252,7 +269,9 @@ msgid_plural "Cohorts" msgstr[0] "समूह" msgid "Session" -msgstr "सत्र" +msgid_plural "Sessions" +msgstr[0] "सत्र" +msgstr[1] "सत्र" msgid "Date of registration" msgstr "पंजीकरण की तिथि" @@ -504,14 +523,12 @@ msgstr "लोड हो रहा है..." msgid "Permission denied" msgstr "अनुमति अस्वीकृत" -msgid "Are You Sure?" -msgstr "क्या आप सुनिश्चित हैं?" - -msgid "Leaving the form will result in the loss of any information entered." -msgstr "फ़ॉर्म छोड़ने से दर्ज की गई सभी जानकारी खो जाएगी।" +msgid "and" +msgstr "तथा" -msgid "Proceed" -msgstr "आगे बढ़ें" +msgid "or" +msgstr "वा" -msgid "Saving" -msgstr "सहेजा जा रहा है" \ No newline at end of file +# For react-select noOptionsMessage prop +msgid "No options" +msgstr "" diff --git a/modules/dataquery/jsx/components/filterableselectgroup.tsx b/modules/dataquery/jsx/components/filterableselectgroup.tsx index 49d937e76c6..a1ad85b5200 100644 --- a/modules/dataquery/jsx/components/filterableselectgroup.tsx +++ b/modules/dataquery/jsx/components/filterableselectgroup.tsx @@ -1,4 +1,5 @@ import Select, {SingleValue} from 'react-select'; +import {useTranslation} from 'react-i18next'; type SelectOption = { label: string, @@ -27,8 +28,10 @@ function FilterableSelectGroup(props: { groups: object, mapGroupName?: (module: string) => string, }) { + const {t} = useTranslation(); const groups: SelectGroup[] = []; - const placeholder = props.placeholder || 'Select a category'; + const placeholder = props.placeholder || + t('Select a category', {ns: 'dataquery'}); for (const [module, subcategories] of Object.entries(props.groups)) { const options: SelectOption[] = []; @@ -71,6 +74,7 @@ function FilterableSelectGroup(props: { return (
{ - return {value: visit, label: visit}; - }) - } - isMulti - onChange={selected} - placeholder='Select Visits' - value={selectedVisits.map( (visit: string): VisitOption => { - return {value: visit, label: visit}; - }) - } - menuPortalTarget={document.body} - styles={ - {menuPortal: +

{t('Visits', {ns: 'loris'})}

+ t('No options', {ns: 'loris'})} menuPortalTarget={document.body} styles={ {menuPortal: @@ -359,12 +364,13 @@ function DefineFields(props: { closeMenuOnSelect={false} />
- setSyncVisits(value) - } /> + setSyncVisits(value) + } />
; } @@ -372,7 +378,9 @@ function DefineFields(props: { fieldList = (
-

{cname} fields

+

{ + t('{{columnName}} fields', {ns: 'dataquery', columnName: cname}) + }

@@ -398,11 +406,11 @@ function DefineFields(props: { }}>
@@ -415,7 +423,7 @@ function DefineFields(props: {
-

Available Fields

+

{t('Available Fields', {ns: 'dataquery'})}

props.allCategories.modules[key]} onChange={props.onCategoryChange} @@ -437,11 +445,13 @@ function DefineFields(props: { alignItems: 'flex-end', marginBottom: '1em', }}> -

Selected Fields

+

{t('Selected Fields', {ns: 'dataquery'})}

+ onClick={props.onClearAll}> + {t('Clear', {ns: 'loris'})} +
void, }) { const [removingIdx, setRemovingIdx] = useState(null); - const [draggingIdx, setDraggingIdx] = useState(null); const [droppingIdx, setDroppingIdx] = useState(null); diff --git a/modules/dataquery/jsx/definefilters.addfiltermodal.tsx b/modules/dataquery/jsx/definefilters.addfiltermodal.tsx index 0c3161f9d81..93fb18f2351 100644 --- a/modules/dataquery/jsx/definefilters.addfiltermodal.tsx +++ b/modules/dataquery/jsx/definefilters.addfiltermodal.tsx @@ -18,21 +18,26 @@ import { VisitOption, } from './types'; import {CategoriesAPIReturn} from './hooks/usedatadictionary'; +import {Trans} from 'react-i18next'; /** * Renders a selectable list of visits * * @param {object} props - React props + * @param {any} props.t - useTranslation * @param {string[]} props.selected - The currently selected visits * @param {string[]} props.options - The valid options * @param {function} props.onChange - callback when the value selected changes + * * @returns {React.ReactElement} - The visit list dropdown */ function VisitList(props: { + t: any, selected: string[], options: string[], onChange: (newvals: string[]) => void, }) { + const {t} = props; const selectOptions: VisitOption[] = props.options.map( (vl) => { return {value: vl, label: vl}; @@ -50,7 +55,8 @@ function VisitList(props: { newvals.map((valobj) => valobj.value) ); }} - placeholder='Select Visits' + placeholder={t('Select Visits', {ns: 'dataquery'})} + noOptionsMessage={() => t('No options', {ns: 'loris'})} value={selectedVisits} menuPortalTarget={document.body} styles={{menuPortal: @@ -70,6 +76,7 @@ function VisitList(props: { * Render a modal window for adding a filter * * @param {object} props - React props + * @param {any} props.t - useTranslation * @param {QueryGroup} props.query - The current query * @param {function} props.closeModal - Callback to close the modal * @param {function} props.addQueryGroupItem - Callback to add criteria to a querygroup @@ -81,6 +88,7 @@ function VisitList(props: { * @returns {React.ReactElement} - The modal window */ function AddFilterModal(props: { + t: any, query: QueryGroup, closeModal: () => void, addQueryGroupItem: (group: QueryGroup, condition: QueryTerm) => void, @@ -92,22 +100,26 @@ function AddFilterModal(props: { module: string, category: string, }) { + const {t} = props; let fieldSelect; let criteriaSelect; let visitSelect; let cardinalityWarning; const [fieldDictionary, setFieldDictionary] - = useState(null); + = useState(null); const [fieldname, setFieldname] = useState(null); const [op, setOp] = useState(null); const [value, setValue] = useState(''); const [selectedVisits, setSelectedVisits] = useState(null); + const dropdownTitle = t('Field', {ns: 'dataquery', count: 99}); if (props.displayedFields) { - const options: { Fields: {[key: string]: string}} = {'Fields': {}}; + const options: { [dropdown: string]: {[key: string]: string}} + = {[dropdownTitle]: {}}; for (const [key, value] of Object.entries(props.displayedFields)) { - options['Fields'][key] = value.description; + options[dropdownTitle][key] = value.description; } + fieldSelect = ; + placeholder={t('Select a field', {ns: 'dataquery'})} />; } if (fieldDictionary) { let valueSelect; if (op) { - valueSelect = valueInput(fieldDictionary, op, value, setValue); + valueSelect = valueInput(fieldDictionary, op, value, setValue, t); } criteriaSelect =
-

Criteria

+

{t('Criteria', {ns: 'dataquery'})}

{ setOp(operator as Operators); }} - placeholder="Select an operator" + placeholder={t('Select an operator', {ns: 'dataquery'})} />
{valueSelect}
@@ -151,8 +163,11 @@ function AddFilterModal(props: { if (fieldDictionary.scope == 'session' && fieldDictionary.visits) { visitSelect =
e.stopPropagation()}> -

for at least one of the following visits

- {t('for at least one of the following visits', + {ns: 'dataquery'})} + @@ -173,10 +188,18 @@ function AddFilterModal(props: {
This field may exist multiple times for a - single {fieldDictionary.scope}. Adding a criteria - based on it means that it must match for at least - one of the data points.
+ }}> + at least one of the data points.' + } + ns="dataquery" + values={{scope: fieldDictionary.scope}} + components={{italic: }} + /> +
; } } @@ -193,8 +216,9 @@ function AddFilterModal(props: { if (!fieldname) { swal.fire({ type: 'error', - title: 'Invalid field', - text: 'You must select a field for the criteria.', + title: t('Invalid field', {ns: 'dataquery'}), + text: t('You must select a field for the criteria.', + {ns: 'dataquery'}), }); reject(); return; @@ -202,8 +226,9 @@ function AddFilterModal(props: { if (!op) { swal.fire({ type: 'error', - title: 'Invalid operator', - text: 'You must select an operator for the criteria.', + title: t('Invalid operator', {ns: 'dataquery'}), + text: t('You must select an operator for the criteria.', + {ns: 'dataquery'}), }); reject(); return; @@ -214,9 +239,9 @@ function AddFilterModal(props: { && op != 'exists' && op != 'notexists') { swal.fire({ type: 'error', - title: 'Invalid value', - text: 'You must enter a value to compare the ' + - 'field against.', + title: t('Invalid value', {ns: 'dataquery'}), + text: t('You must enter a value to compare the field against.', + {ns: 'dataquery'}), }); reject(); return; @@ -227,8 +252,8 @@ function AddFilterModal(props: { if (!selectedVisits || selectedVisits.length == 0) { swal.fire({ type: 'error', - title: 'Invalid visits', - text: 'No visits selected for criteria.', + title: t('Invalid visits', {ns: 'dataquery'}), + text: t('No visits selected for criteria.', {ns: 'dataquery'}), }); reject(); return; @@ -257,13 +282,13 @@ function AddFilterModal(props: { } ); return ( -
-

Field

+

{t('Field', {ns: 'dataquery', count: 1})}

void + setValue: (val: string) => void, + t: any ) { const vs: string = value as string; switch (op) { @@ -384,6 +412,8 @@ function valueInput(fielddict: FieldDictionary, onUserInput={(name: string, value: string) => setValue(value)} />; } + const dropdownTitle = t('Value', {ns: 'dataquery'}); + switch (fielddict.type) { case 'date': return setValue(value)} />; case 'boolean': return { setValue(value); }} - placeholder="Select a value" + placeholder={t('Select a value', {ns: 'dataquery'})} />; case 'enumeration': const opts: {[key: string]: string} = {}; @@ -449,11 +479,11 @@ function valueInput(fielddict: FieldDictionary, />; } return { setValue(value); }} - placeholder="Select a value" + placeholder={t('Select a value', {ns: 'dataquery'})} />; default: return void, closeModal: () => void, }) { + const {t} = useTranslation('dataquery'); const [csvFile, setCSVFile] = useState(null); const [csvHeader, setCSVHeader] = useState(false); const [csvType, setCSVType] = useState('candidate'); @@ -31,15 +33,17 @@ function ImportCSVModal(props: { if (!csvFile) { swal.fire({ type: 'error', - title: 'No CSV Uploaded', - text: 'Please upload a CSV file before submitting.', + title: t('No CSV Uploaded', {ns: 'dataquery'}), + text: t( + 'Please upload a CSV file before submitting.', + {ns: 'dataquery'} + ), }); reject(); return; } resolve(null); - } - ); + }); const candIDRegex = new RegExp('^[1-9][0-9]{5}$'); @@ -53,8 +57,8 @@ function ImportCSVModal(props: { console.error(value.errors); swal.fire({ type: 'error', - title: 'Invalid CSV', - text: 'Could not parse CSV file', + title: t('Invalid CSV', {ns: 'dataquery'}), + text: t('Could not parse CSV file', {ns: 'dataquery'}), }); setCSVFile(null); return; @@ -65,8 +69,8 @@ function ImportCSVModal(props: { if (!value.data || value.data.length <= startLine) { swal.fire({ type: 'error', - title: 'Empty CSV', - text: 'The uploaded CSV file is empty.', + title: t('Empty CSV', {ns: 'dataquery'}), + text: t('The uploaded CSV file is empty.', {ns: 'dataquery'}), }); setCSVFile(null); return; @@ -80,10 +84,10 @@ function ImportCSVModal(props: { if (value.data[i].length != expectedLength) { swal.fire({ type: 'error', - title: 'Invalid CSV', - text: 'Expected ' + expectedLength + ' columns in CSV.' - + ' Got ' + value.data[i].length + ' on line ' + - (i+1) + '.', + title: t('Invalid CSV', {ns: 'dataquery'}), + text: t('Expected {{expectedLength}} columns in CSV. ' + +'Got {{gotLength}} on line {{line}}.', {ns: 'dataquery', + expectedLength, gotLength: value.data[i].length, line: i+1}), }); setCSVFile(null); return; @@ -92,10 +96,9 @@ function ImportCSVModal(props: { if (candIDRegex.test(value.data[i][0]) !== true) { swal.fire({ type: 'error', - title: 'Invalid DCC ID', - text: 'Invalid DCC ID (' + value.data[i][0] - + ') on line ' - + (i+1) + '.', + title: t('Invalid DCCID', {ns: 'dataquery'}), + text: t('Invalid DCCID ({{id}}) on line {{line}}.', + {ns: 'dataquery', id: value.data[i][0], line: i+1}), }); setCSVFile(null); return; @@ -149,7 +152,7 @@ function ImportCSVModal(props: { marginTop: '1em', }; - return
-
CSV containing list of
+
{t('CSV containing list of', + {ns: 'dataquery'})}
setCSVType('candidate')} - /> Candidates + /> {t('Candidate', {ns: 'loris', count: 99})} setCSVType('session')} - /> Sessions + /> {t('Session', {ns: 'loris', count: 99})}
-
Candidate identifier type
+
{t('Candidate identifier type', + {ns: 'dataquery'})}
setIdType('CandID')} - /> DCC ID + /> {t('DCCID', {ns: 'loris'})} setIdType('PSCID')} - /> PSCID + /> {t('PSCID', {ns: 'loris'})}
- Does CSV contain a header line? + {t('Does CSV contain a header line?', {ns: 'dataquery'})}
setCSVHeader(true)} - /> Yes + /> {t('Yes', {ns: 'loris'})} setCSVHeader(false)} - /> No + /> {t('No', {ns: 'loris'})}
-
CSV File
+
{t('CSV File', {ns: 'dataquery'})}
void, removeQueryGroupItem: (group: QueryGroup, i: number) => QueryGroup, }) : React.ReactElement { + const {t} = useTranslation('dataquery'); let displayquery: React.ReactNode = null; const [addModal, setAddModal] = useState(false); const [csvModal, setCSVModal] = useState(false); @@ -118,7 +120,8 @@ function DefineFilters(props: { const mapModuleName = props.mapModuleName; const mapCategoryName = props.mapCategoryName; - const advancedLabel = showAdvanced ? 'Hide Advanced' : 'Show Advanced'; + const advancedLabel = showAdvanced ? t('Hide Advanced', + {ns: 'dataquery'}) : t('Show Advanced', {ns: 'dataquery'}); let advancedButtons; const toggleAdvancedButton = (
@@ -138,16 +141,14 @@ function DefineFilters(props: { if (showAdvanced) { advancedButtons = (
-

The "nested groups" options are advanced options for queries - that do not have any specific condition at the - base of the query. - Use Add nested "or" condition groups if - you need to build a query of the form. - (a or b) and (c or d) [or (e and f)..]. -

+

{t('The "nested groups" options are advanced options for ' + +'queries that do not have any specific condition at the base' + +' of the query. Use Add nested "or" condition groups if you' + +' need to build a query of the form. (a or b) and (c or d)' + +' [or (e and f)..]', {ns: 'dataquery'})}

{ e.preventDefault(); props.query.operator = 'and'; @@ -155,14 +156,12 @@ function DefineFilters(props: { }} />
-

- Use Add nested "and" condition groups if you - need to build a query of the form - (a and b) or (c and d) [or (e and f)..]. -

+

{t('Use Add nested "and" condition groups if you need to ' + +'build a query of the form (a and b) or (c and d) ' + +'[or (e and f)..]', {ns: 'dataquery'})}

{ e.preventDefault(); props.query.operator = 'or'; @@ -178,23 +177,23 @@ function DefineFilters(props: { displayquery =
-

Currently querying for ALL candidates.

-

You can add conditions by clicking one of the buttons below.

-

Click Add Condition to add one or more conditions - to your filters (ie. "Date Of Birth < 2015-02-15"). This is - most likely where you want to start your filters. -

-

You can also import a population from a CSV by clicking - the Import from CSV button.

-

The advanced options are for queries that do not have - a condition to add at the base of the query.

+

{t('Currently querying for ALL candidates.', {ns: 'dataquery'})}

+

{t('You can add conditions by clicking one of the buttons below.', + {ns: 'dataquery'})}

+

{t('Click Add Condition to add one or more conditions to your' + +' filters (ie. "Date Of Birth < 2015-02-15"). This is most likely' + +' where you want to start your filters.', {ns: 'dataquery'})}

+

{t('You can also import a population from a CSV by clicking the' + +' Import from CSV button.', {ns: 'dataquery'})}

+

{t('The advanced options are for queries that do not have a ' + +'condition to add at the base of the query.', {ns: 'dataquery'})}

{ e.preventDefault(); @@ -204,7 +203,7 @@ function DefineFilters(props: {
{ e.preventDefault(); @@ -231,29 +230,25 @@ function DefineFilters(props: { advancedButtons = (
-

Use New "and" subgroup if the rest of the - query you need to write is a subgroup consisting - of "and" conditions. ie your query is of the form: -

- (your condition above) or (c and d [and e and f..]) -
-

+

{t('Use New "and" subgroup if the rest of the query you' + +' need to write is a subgroup consisting of "and" ' + +'conditions. ie your query is of the form: (your condition' + +' above) or (c and d [and e and f..])', + {ns: 'dataquery'})}

{ e.preventDefault(); props.query.operator = 'or'; props.addNewQueryGroup(props.query); }} /> -

Use New "or" subgroup if the rest of the - query you need to write is a subgroup consisting - of "or" conditions. ie your query is of the form: -

- (your condition above) and (c or d [or e or f..]) -
-

+

{t('Use New "or" subgroup if the rest of the query you ' + +'need to write is a subgroup consisting of "or" ' + +'conditions. ie your query is of the form: (your ' + +'condition above) and (c or d [or e or f..])', + {ns: 'dataquery'})}

{ e.preventDefault(); props.query.operator = 'and'; @@ -265,7 +260,8 @@ function DefineFilters(props: { } // buttons for 1. Add "and" condition 2. Add "or" condition displayquery = (
-

Currently querying for any candidates with:

+

{t('Currently querying for any candidates with:', + {ns: 'dataquery'})}

@@ -282,7 +278,7 @@ function DefineFilters(props: { />
{ const newquery = props.removeQueryGroupItem( props.query, @@ -299,7 +295,7 @@ function DefineFilters(props: {
{ e.preventDefault(); @@ -307,7 +303,7 @@ function DefineFilters(props: { setAddModal(true); }} /> { e.preventDefault(); @@ -325,7 +321,8 @@ function DefineFilters(props: { // Add buttons are delegated to the QueryTree rendering so they // can be placed at the right level displayquery =
-

Currently querying for any candidates with:

+

{t('Currently querying for any candidates with:', + {ns: 'dataquery'})}

setAddModal(false)} addQueryGroupItem={(querygroup, condition) => { @@ -377,7 +375,14 @@ function DefineFilters(props: { const matchCount = queryMatches === null ?
 
// So the header doesn't jump around - :
Query matches {queryMatches} candidates
; + :
+ }} + /> +
; return (
{modal} {csvModalHTML} @@ -386,13 +391,14 @@ function DefineFilters(props: { justifyContent: 'space-between', alignItems: 'baseline', }}> -

Current Query

+

{t('Current Query', {ns: 'dataquery'})}

{matchCount}
- Note that only candidates which you have permission to - access in LORIS are included in results. Number of - results may vary from other users running the same query. + {t('Note that only candidates which you have permission to ' + +'access in LORIS are included in results. Number of results' + +' may vary from other users running the same query.', + {ns: 'dataquery'})} {displayquery}
diff --git a/modules/dataquery/jsx/fielddisplay.tsx b/modules/dataquery/jsx/fielddisplay.tsx index e58cad1f60a..20cec91712e 100644 --- a/modules/dataquery/jsx/fielddisplay.tsx +++ b/modules/dataquery/jsx/fielddisplay.tsx @@ -1,6 +1,6 @@ import {FullDictionary} from './types'; - import getDictionaryDescription from './getdictionarydescription'; +import {useTranslation} from 'react-i18next'; /** * A single field to display @@ -18,11 +18,11 @@ function FieldDisplay(props: { module: string, category: string, fieldname: string, - fulldictionary: FullDictionary, mapModuleName: (module: string) => string, mapCategoryName: (module: string, category: string) => string, }) { + const {t} = useTranslation('dataquery'); const description = getDictionaryDescription( props.module, props.category, @@ -35,8 +35,10 @@ function FieldDisplay(props: { {description}
+ {t('Category', {ns: 'dataquery'})}: {props.mapCategoryName(props.module, props.category)} -  ({props.mapModuleName(props.module)}) +  ({t('Module', {ns: 'loris'})}: + {props.mapModuleName(props.module)})
); diff --git a/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx b/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx index ba65fb9c5b7..c3c405f64cc 100644 --- a/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx +++ b/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx @@ -1,4 +1,5 @@ import React, {useEffect} from 'react'; +import {useTranslation} from 'react-i18next'; import Breadcrumbs from 'jsx/Breadcrumbs'; // Declared in smarty main.tpl @@ -15,11 +16,12 @@ function useBreadcrumbs( activeTab: string, setActiveTab: (newtab: string) => void ) { + const {t} = useTranslation('dataquery'); // update breadcrumbs breadcrumbs useEffect(() => { const breadcrumbs = [ { - text: 'Data Query Tool (Beta)', + text: t('Data Query Tool (Beta)', {ns: 'dataquery'}), /** * OnClick handler for the main breadcrumb * @@ -36,7 +38,7 @@ function useBreadcrumbs( || activeTab == 'DefineFilters' || activeTab == 'ViewData') { breadcrumbs.push({ - text: 'Define Fields', + text: t('Define Fields', {ns: 'dataquery'}), /** * OnClick handler for the define fields breadcrumb * @@ -52,7 +54,7 @@ function useBreadcrumbs( if (activeTab == 'DefineFilters' || activeTab == 'ViewData') { breadcrumbs.push({ - text: 'Define Filters', + text: t('Define Filters', {ns: 'dataquery'}), /** * OnClick handler for the define filters breadcrumb * @@ -68,7 +70,7 @@ function useBreadcrumbs( if (activeTab == 'ViewData') { breadcrumbs.push({ - text: 'View Data', + text: t('View Data', {ns: 'dataquery'}), /** * OnClick handler for the View Data breadcrumb * @@ -90,7 +92,7 @@ function useBreadcrumbs( />, ); } - }, [activeTab]); + }, [activeTab, t]); } export default useBreadcrumbs; diff --git a/modules/dataquery/jsx/hooks/usedatadictionary.tsx b/modules/dataquery/jsx/hooks/usedatadictionary.tsx index 5f7b93dd8c4..679a7500142 100644 --- a/modules/dataquery/jsx/hooks/usedatadictionary.tsx +++ b/modules/dataquery/jsx/hooks/usedatadictionary.tsx @@ -1,4 +1,5 @@ import {useState, useEffect} from 'react'; +import {useTranslation} from 'react-i18next'; import {ModuleDictionary, FullDictionary} from '../types'; @@ -23,6 +24,7 @@ export type CategoriesAPIReturn = { */ function useCategories(): CategoriesAPIReturn|null { const [categories, setCategories] = useState(null); + const {t} = useTranslation('dataquery'); useEffect(() => { if (categories !== null) { return; @@ -30,7 +32,7 @@ function useCategories(): CategoriesAPIReturn|null { fetch('/dictionary/categories', {credentials: 'same-origin'}) .then((resp) => { if (!resp.ok) { - throw new Error('Invalid response'); + throw new Error(t('Invalid response', {ns: 'dataquery'})); } return resp.json(); }).then((result) => { @@ -59,7 +61,7 @@ function useDataDictionary(): DataDictionaryReturnType { // typescript says the key is always defined when we try and check if // it's set, need to figure out the correct way to do that, for now just use any const [pendingModules, setPendingModules] = useState({}); - + const {t} = useTranslation('dataquery'); /** * Fetch a module's dictionary and cache it into fulldictionary. * @@ -80,7 +82,7 @@ function useDataDictionary(): DataDictionaryReturnType { {credentials: 'same-origin'} ).then((resp) => { if (!resp.ok) { - throw new Error('Invalid response'); + throw new Error(t('Invalid response', {ns: 'dataquery'})); } return resp.json(); }).then((result) => { diff --git a/modules/dataquery/jsx/index.tsx b/modules/dataquery/jsx/index.tsx index 4b0d3a88b54..e0608297df5 100644 --- a/modules/dataquery/jsx/index.tsx +++ b/modules/dataquery/jsx/index.tsx @@ -13,9 +13,12 @@ import useBreadcrumbs from './hooks/usebreadcrumbs'; import useVisits from './hooks/usevisits'; import useQuery from './hooks/usequery'; import {useSharedQueries, useLoadQueryFromURL} from './hooks/usesharedqueries'; - import {useDataDictionary, useCategories} from './hooks/usedatadictionary'; import {ModuleDictionary, DictionaryCategory} from './types'; +// @ts-ignore +import i18n from 'I18nSetup'; +import {withTranslation} from 'react-i18next'; +import hiStrings from '../locale/hi/LC_MESSAGES/dataquery.json'; type ActiveCategoryType = { module: string, @@ -64,11 +67,14 @@ function useActiveCategory( * Return the main page for the DQT * * @param {object} props - React props + * @param {any} props.t - useTranslation * @param {boolean} props.queryAdmin - true if the current user has permission to administer study queries * @param {string} props.username - The user accessing the app + * * @returns {React.ReactElement} - The main page of the app */ function DataQueryApp(props: { + t: any, queryAdmin: boolean, username: string }) { @@ -99,8 +105,9 @@ function DataQueryApp(props: { useLoadQueryFromURL(loadQuery); + const {t} = props; if (!categories) { - return
Loading...
; + return
{t('Loading...', {ns: 'loris'})}
; } let content; @@ -221,16 +228,15 @@ function DataQueryApp(props: { break; case 'ViewData': content = ; break; default: - content =
Invalid tab
; + content =
{t('Invalid tab', {ns: 'dataquery'})}
; } return
{content}
@@ -244,14 +250,18 @@ function DataQueryApp(props: { declare const loris: any; window.addEventListener('load', () => { + i18n.addResourceBundle('ja', 'dataquery', {}); + i18n.addResourceBundle('hi', 'dataquery', hiStrings); + const Index = withTranslation( + ['dataquery', 'loris'] + )(DataQueryApp); const element = document.getElementById('lorisworkspace'); if (!element) { throw new Error('Missing lorisworkspace'); } const root = createRoot(element); - root.render( - , diff --git a/modules/dataquery/jsx/nextsteps.tsx b/modules/dataquery/jsx/nextsteps.tsx index 59a88cdfac2..6503a2f441b 100644 --- a/modules/dataquery/jsx/nextsteps.tsx +++ b/modules/dataquery/jsx/nextsteps.tsx @@ -1,4 +1,5 @@ import React, {useState} from 'react'; +import {useTranslation} from 'react-i18next'; import {APIQueryField} from './types'; import {ButtonElement} from 'jsx/Form'; import {QueryGroup} from './querydef'; @@ -19,17 +20,18 @@ function NextSteps(props: { page: string, changePage: (newpage: string) => void, }) { + const {t} = useTranslation('dataquery'); const [expanded, setExpanded] = useState(true); const steps: React.ReactElement[] = []; const canRun = (props.fields && props.fields.length > 0); const fieldLabel = (props.fields && props.fields.length > 0) - ? 'Modify Fields' - : 'Choose Fields'; + ? t('Modify Fields', {ns: 'dataquery'}) + : t('Choose Fields', {ns: 'dataquery'}); const filterLabel = (props.filters && props.filters.group.length > 0) - ? 'Modify Filters' - : 'Add Filters'; + ? t('Modify Filters', {ns: 'dataquery'}) + : t('Add Filters', {ns: 'dataquery'}); switch (props.page) { case 'Info': if (canRun) { @@ -48,7 +50,7 @@ function NextSteps(props: { onUserInput={() => props.changePage('DefineFilters')} />); steps.push( props.changePage('ViewData')} @@ -72,7 +74,7 @@ function NextSteps(props: { />); if (canRun) { steps.push( props.changePage('ViewData')} @@ -82,7 +84,7 @@ function NextSteps(props: { case 'DefineFilters': if (canRun) { steps.push( props.changePage('ViewData')} @@ -148,7 +150,7 @@ function NextSteps(props: { paddingRight: '14px', }}>
-

Next Steps

+

{t('Next Steps', {ns: 'dataquery'})}

{steps}
diff --git a/modules/dataquery/jsx/querytree.tsx b/modules/dataquery/jsx/querytree.tsx index 6b045f65275..f77142f323f 100644 --- a/modules/dataquery/jsx/querytree.tsx +++ b/modules/dataquery/jsx/querytree.tsx @@ -3,7 +3,8 @@ import {QueryGroup, QueryTerm} from './querydef'; import {CriteriaTerm} from './criteriaterm'; import {ButtonElement} from 'jsx/Form'; import {FullDictionary} from './types'; -import {useEffect} from 'react'; // already present +import {useEffect} from 'react'; +import {useTranslation} from 'react-i18next'; /** * Alternate background colour for a QueryTree @@ -39,6 +40,7 @@ function alternateColour(c: string): string { * @param {object} props.mapModuleName - Function to map the backend module name to a user friendly name * @param {object} props.mapCategoryName - Function to map the backend category name to a user friendly name * @param {object} props.fulldictionary - The dictionary of all modules that have been loaded + * @param {function} props.setDeleteItemIndex - Callback to set or clear the index of the item marked for deletion * @returns {React.ReactElement} - the react element */ function QueryTree(props: { @@ -61,6 +63,7 @@ function QueryTree(props: { mapCategoryName: (module: string, category: string) => string, }) { const [deleteItemIndex, setDeleteItemIndex] = useState(null); + const {t} = useTranslation('dataquery'); useEffect(() => { // Reset strikethrough when group is empty or changed @@ -114,7 +117,7 @@ function QueryTree(props: { if (item instanceof QueryTerm) { const deleteIcon = props.removeQueryGroupItem ? (
- setDeleteItemIndex(i)} @@ -190,7 +193,7 @@ function QueryTree(props: { marginLeft: 10, }}>
- Group does not have any items. + {t('Group does not have any items.', {ns: 'dataquery'})}
; break; @@ -204,8 +207,8 @@ function QueryTree(props: { marginLeft: 10, }}>
- Group only has 1 item. A group with only 1 item is equivalent - to not having the group. + {t('Group only has 1 item. A group with only 1 item is equivalent' + +' to not having the group.', {ns: 'dataquery'})}
; break; @@ -248,8 +251,7 @@ function QueryTree(props: { deleteGroupHTML = (
Query not yet run; + return

{t('Query not yet run', {ns: 'dataquery'})}

; } return (
- + - {props.value} of {props.max} candidates + {t('{{value}} of {{max}} candidates', + {ns: 'dataquery', value: props.value, max: props.max})}
); case 'headers': return (
- + - {props.value} of {props.max} columns + {t('{{value}} of {{max}} columns', {ns: 'dataquery', + value: props.value, max: props.max})}
); case 'dataorganization': return (
- + - {props.value} of {props.max} columns + {t('{{value}} of {{max}} columns', {ns: 'dataquery', + value: props.value, max: props.max})}
); } - return

Invalid progress type: {props.type}

; + return

{t('Invalid progress type: {{type}}', {ns: 'dataquery', + type: props.type})}

; } type RunQueryType = { @@ -298,6 +307,7 @@ function useDataOrganization( * The View Data tab * * @param {object} props - React props + * @param {any} props.t - useTranslation * @param {APIQueryField[]} props.fields - The selected fields * @param {QueryGroup} props.filters - The selected filters * @param {object} props.fulldictionary - the data dictionary @@ -305,11 +315,13 @@ function useDataOrganization( * @returns {React.ReactElement} - The ViewData tab */ function ViewData(props: { + t: any fields: APIQueryField[], filters: QueryGroup, onRun: () => void fulldictionary: FullDictionary, }) { + const {t} = props; const [visitOrganization, setVisitOrganization] = useState('inline'); const [headerDisplay, setHeaderDisplay] @@ -335,7 +347,7 @@ function ViewData(props: { } else { switch (organizedData['status']) { case null: - return queryTable =

Query not yet run

; + return queryTable =

{t('Query not yet run')}

; case 'headers': queryTable = { return {show: true, label: val}; @@ -373,6 +385,7 @@ function ViewData(props: { props.fulldictionary, emptyVisits, enumDisplay, + props.t ) } hide={ @@ -386,7 +399,7 @@ function ViewData(props: { } catch (e) { // OrganizedMapper/Formatter can throw an error // before the loading is complete - return
Loading..
; + return
{t('Loading...', {ns: 'loris'})}
; } break; default: @@ -398,7 +411,7 @@ function ViewData(props: { setEmptyVisits(value) @@ -409,9 +422,9 @@ function ViewData(props: { (No data); + return {t('(No data)', {ns: 'dataquery'})}; } switch (fielddict.cardinality) { case 'many': - return (Not implemented); + return {t('(Not implemented)', {ns: 'dataquery'})}; case 'single': case 'unique': case 'optional': return ; default: return ( - (Internal Error. Unhandled cardinality: - {fielddict.cardinality}) - + {t('(Internal Error. Unhandled cardinality: {{cardinality}})', + {ns: 'dataquery', cardinality: fielddict.cardinality})} ); } } @@ -932,7 +947,7 @@ function organizedFormatter( return null; } catch (e) { console.error(e); - return (Internal error); + return {t('(Internal error)', {ns: 'dataquery'})}; } }; let theval = visitval(visit, cell); @@ -940,7 +955,7 @@ function organizedFormatter( return
; } if (theval === null) { - theval = (No data); + theval = {t('(No data)', {ns: 'dataquery'})}; } return (
(Internal error); + return {t('(Internal error)', {ns: 'dataquery'})}; } return null; }; @@ -1006,7 +1021,7 @@ function organizedFormatter( return
; } if (theval === null) { - theval = (No data); + theval = {t('(No data)', {ns: 'dataquery'})}; } return (
{cells.map((cell: LongitudinalExpansion) => { if (cell.value === null) { - return (No data); + return {t('(No data)', {ns: 'dataquery'})}; } return ( @@ -1102,7 +1117,7 @@ function organizedFormatter( fieldNo: number ): ReactNode => { if (cell === null) { - return No data for visit; + return {t('No data for visit', {ns: 'dataquery'})}; } if (fieldNo == 0) { // automatically added Visit column diff --git a/modules/dataquery/jsx/welcome.adminquerymodal.tsx b/modules/dataquery/jsx/welcome.adminquerymodal.tsx index ea7a839c5b9..e93904c03c5 100644 --- a/modules/dataquery/jsx/welcome.adminquerymodal.tsx +++ b/modules/dataquery/jsx/welcome.adminquerymodal.tsx @@ -2,6 +2,7 @@ import Modal from 'jsx/Modal'; import swal from 'sweetalert2'; import {useState} from 'react'; import {CheckboxElement, TextboxElement, FieldsetElement} from 'jsx/Form'; +import {useTranslation} from 'react-i18next'; /** * Render a modal window for naming a query @@ -25,6 +26,7 @@ function AdminQueryModal(props: { ) => void, }) { + const {t} = useTranslation('dataquery'); const [queryName, setQueryName] = useState(props.defaultName || ''); const [topQuery, setTopQuery] = useState(true); const [dashboardQuery, setDashboardQuery] = useState(true); @@ -40,7 +42,8 @@ function AdminQueryModal(props: { if (queryName.trim() == '') { swal.fire({ type: 'error', - text: 'Must provide a query name to pin query as.', + text: t('Must provide a query name to pin query as.', + {ns: 'dataquery'}), }); reject(); return; @@ -48,7 +51,8 @@ function AdminQueryModal(props: { if (!topQuery && !dashboardQuery && !loginQuery) { swal.fire({ type: 'error', - text: 'Must pin as study query, to dashboard, or to the login page.', + text: t('Must pin as study query, to dashboard, or to the ' + +'login page.', {ns: 'dataquery'}), }); reject(); return; @@ -63,16 +67,16 @@ function AdminQueryModal(props: { } return sbmt; }; - return + legend={t('Study Query', {ns: 'dataquery', count: 1})}> setQueryName(value) } @@ -82,11 +86,11 @@ function AdminQueryModal(props: { onUserInput={ (name: string, value: boolean) => setTopQuery(value) } - label='Pin Study Query' + label={t('Pin Study Query', {ns: 'dataquery'})} /> setDashboardQuery(value) @@ -94,7 +98,7 @@ function AdminQueryModal(props: { /> setLoginQuery(value) diff --git a/modules/dataquery/jsx/welcome.namequerymodal.tsx b/modules/dataquery/jsx/welcome.namequerymodal.tsx index 189e5ac880e..a726888585c 100644 --- a/modules/dataquery/jsx/welcome.namequerymodal.tsx +++ b/modules/dataquery/jsx/welcome.namequerymodal.tsx @@ -2,6 +2,7 @@ import Modal from 'jsx/Modal'; import swal from 'sweetalert2'; import {useState} from 'react'; import {TextboxElement, FieldsetElement} from 'jsx/Form'; +import {useTranslation} from 'react-i18next'; /** * Render a modal window for naming a query @@ -19,6 +20,7 @@ function NameQueryModal(props: { closeModal: () => void, onSubmit: (name: string) => void, }) { + const {t} = useTranslation('dataquery'); const [queryName, setQueryName] = useState(props.defaultName || ''); /** * Convert the onSubmit callback function to a promise expected by Modal @@ -30,7 +32,7 @@ function NameQueryModal(props: { if (queryName == '') { swal.fire({ type: 'error', - text: 'Must provide a query name.', + text: t('Must provide a query name.', {ns: 'dataquery'}), }); reject(); return; @@ -42,16 +44,16 @@ function NameQueryModal(props: { } return sbmt; }; - return + legend={t('Query name', {ns: 'dataquery'})}> setQueryName(value) } diff --git a/modules/dataquery/jsx/welcome.tsx b/modules/dataquery/jsx/welcome.tsx index 86103fcee4c..9ec575dde14 100644 --- a/modules/dataquery/jsx/welcome.tsx +++ b/modules/dataquery/jsx/welcome.tsx @@ -12,6 +12,9 @@ import {ButtonElement, CheckboxElement, TextboxElement} from 'jsx/Form'; import {APIQueryField} from './types'; import {FullDictionary} from './types'; import {FlattenedField, FlattenedQuery, VisitOption} from './types'; +import {useTranslation} from 'react-i18next'; +import 'I18nSetup'; + /** * Return the welcome tab for the DQT * @@ -52,6 +55,7 @@ function Welcome(props: { mapCategoryName: (module: string, category: string) => string, fulldictionary:FullDictionary, }) { + const {t} = useTranslation('dataquery'); const panels: { title: string, content: React.ReactElement, @@ -61,21 +65,21 @@ function Welcome(props: { }[] = []; if (props.topQueries.length > 0) { panels.push({ - title: 'Study Queries', + title: t( + 'Study Query', + {ns: 'dataquery', count: props.topQueries.length} + ), content: (
@@ -87,7 +91,7 @@ function Welcome(props: { }); } panels.push({ - title: 'Instructions', + title: t('Instructions', {ns: 'dataquery'}), content: 0} onContinue={props.onContinue} @@ -97,22 +101,18 @@ function Welcome(props: { id: 'p2', }); panels.push({ - title: 'Recent Queries', + title: t('Recent Queries', {ns: 'dataquery'}), content: (
0) { panels.push({ - title: 'Shared Queries', + title: t('Shared Queries', {ns: 'dataquery'}), content: (
@@ -160,7 +157,7 @@ function Welcome(props: { textAlign: 'center', padding: '30px 0 0 0', }}> - Welcome to the Data Query Tool + {t('Welcome to the Data Query Tool', {ns: 'dataquery'})}
@@ -207,6 +204,7 @@ function QueryList(props: { mapCategoryName: (module: string, category: string) => string, fulldictionary:FullDictionary, }) { + const {t} = useTranslation('dataquery'); const [nameModalID, setNameModalID] = useState(null); const [adminModalID, setAdminModalID] = useState(null); const [queryName, setQueryName] = useState(null); @@ -491,26 +489,28 @@ function QueryList(props: { }); } const starFilter = props.starQuery ? - setOnlyStarred(value) - }/> : ; + setOnlyStarred(value) + }/> : ; const shareFilter = props.shareQuery ? - setOnlyShared(value) - }/> + setOnlyShared(value) + }/> : ; // Use whether shareQuery prop is defined as proxy // to determine if this is a shared query or a recent // query list const duplicateFilter = props.shareQuery ? setQueryFilter(value) @@ -538,20 +538,22 @@ function QueryList(props: { }}> {starFilter} {shareFilter} - setOnlyNamed(value) - }/> + setOnlyNamed(value) + }/> {duplicateFilter} - - setFullQuery(!value) - }/> + + setFullQuery(!value) + }/>
@@ -622,9 +624,10 @@ function QueryListCriteria(props: { fulldictionary: FullDictionary, criteria: QueryGroup }) { + const {t} = useTranslation('dataquery'); if (!props.criteria || !props.criteria.group || props.criteria.group.length == 0) { - return (No filters for query); + return {t('(No filters for query)', {ns: 'dataquery'})}; } return ( {displayedRange}
; } @@ -715,6 +720,7 @@ function SingleQueryDisplay(props: { mapCategoryName: (module: string, category: string) => string, fulldictionary:FullDictionary, }) { + const {t} = useTranslation('dataquery'); const [showFullQuery, setShowFullQuery] = useState(props.showFullQueryDefault); // Reset the collapsed state if the checkbox gets toggled @@ -732,7 +738,7 @@ function SingleQueryDisplay(props: { onClick={ () => props.unstarQuery(query.QueryID) } - title="Unstar" + title={t('Unstar', {ns: 'dataquery'})} className="fa-stack"> props.starQuery(query.QueryID) } @@ -758,7 +764,7 @@ function SingleQueryDisplay(props: { if (query.Public) { sharedIcon = props.unshareQuery(query.QueryID) @@ -767,7 +773,7 @@ function SingleQueryDisplay(props: { } else { sharedIcon = props.shareQuery(query.QueryID) @@ -800,15 +806,15 @@ function SingleQueryDisplay(props: { ); swal.fire({ type: 'success', - title: 'Query Loaded', - text: 'Successfully loaded query.', + title: t('Query Loaded', {ns: 'dataquery'}), + text: t('Successfully loaded query.', {ns: 'dataquery'}), }); }; const loadIcon = ; const pinIcon = props.queryAdmin - ? { @@ -825,18 +831,21 @@ function SingleQueryDisplay(props: { let desc = query.Name ? {query.Name} -  (Run at {query.RunTime}) +  {t('(Run at {{runTime}})', {ns: 'dataquery', + runTime: query.RunTime})} - : You ran this query at {query.RunTime}; + : {t('You ran this query at {{runTime}}', {ns: 'dataquery', + runTime: query.RunTime})}; if (!props.includeRuns) { desc = query.Name ? {query.Name} - : You ran this query; + : {t('You ran this query', {ns: 'dataquery'})}; } const nameIcon = { props.setDefaultModalQueryName(query.Name || ''); props.setNameModalID(query.QueryID); @@ -848,16 +857,18 @@ function SingleQueryDisplay(props: { const desc = query.Name ? {query.Name} -  (Shared by {query.SharedBy.join(', ')}) +  {t('(Shared by {{sharedBy}})', {ns: 'dataquery', + sharedBy: query.SharedBy.join(', ')})} - : Query shared by {query.SharedBy.join(', ')}; + : {t('Query shared by {{sharedBy}}', {ns: 'dataquery', + sharedBy: query.SharedBy.join(', ')})}; msg =
{desc}  {loadIcon}{pinIcon}
; } else if (query.Name || query.AdminName) { const name = props.useAdminName ? query.AdminName : query.Name; const unpinIcon = props.queryAdmin - ? { @@ -876,7 +887,7 @@ function SingleQueryDisplay(props: { const queryDisplay = !showFullQuery ?
:
-

Fields

+

{t('Field', {ns: 'dataquery', count: 99})}

{query.fields.map( (fieldobj, fidx) => {query.criteria ?
-

Filters

+

{ + t('Filter', {ns: 'dataquery', count: query.criteria.group.length}) + }

void, }) { + const {t} = useTranslation('dataquery'); return @@ -1040,13 +1054,16 @@ function ShareIcon(props: { * An icon to name a query * * @param {object} props - React props + * @param {any} props.t - useTranslation * @param {function} props.onClick - Handler to call when icon clicked + * * @returns {React.ReactElement} - The React element */ function NameIcon(props: { - onClick?: () => void + t: any, + onClick?: () => void }): React.ReactElement { - return ( void, hasStudyQueries: boolean, }): React.ReactElement { + const {t} = useTranslation('dataquery'); const studyQueriesParagraph = props.hasStudyQueries ? ( -

Above, there is also a Study Queries panel. This - are a special type of shared queries that have been pinned - by a study administer to always display at the top of this - page.

+

+ {t('Above, there is also a Study Queries panel. This are a' + +' special type of shared queries that have been pinned by a study' + +' administer to always display at the top of this page.', + {ns: 'dataquery'})} +

) : ''; return (
-

The data query tool allows you to query data - within LORIS. There are three steps to defining - a query: -

+

{t('The data query tool allows you to query data within LORIS. ' + +'There are three steps to defining a query:', {ns: 'dataquery'})}

    -
  1. First, you must select the fields that you're - interested in on the Define Fields - page.
  2. -
  3. Next, you can optionally define filters on the - Define Filters page to restrict - the population that is returned.
  4. -
  5. Finally, you view your query results on - the View Data page
  6. +
  7. {t('First, you must select the fields that you\'re interested in' + +' on the Define Fields page.', {ns: 'dataquery'})}
  8. +
  9. {t('Next, you can optionally define filters on the Define ' + +'Filters page to restrict the population that is returned.', + {ns: 'dataquery'})}
  10. +
  11. {t('Finally, you view your query results on the View Data page', + {ns: 'dataquery'})}
-

The Next Steps on the bottom right of your - screen always the context-sensitive next steps that you - can do to build your query.

-

Your recently run queries will be displayed in the - Recent Queries panel below. Instead of building - a new query, you can reload a query that you've recently run - by clicking on the icon next to the query.

-

Queries can be shared with others by clicking the - icon. This will cause the query to be shared with all users who - have access to the fields used by the query. It will display - in a Shared Queries panel below the - Recent Queries.

-

You may also give a query a name at any time by clicking the - icon. This makes it easier to find queries you care - about by giving them an easier to remember name that can be used - for filtering. When you share a query, the name will be shared - along with it.

+

{t('The Next Steps on the bottom right of your screen always the ' + +'context-sensitive next steps that you can do to build your query.', + {ns: 'dataquery'})}

+

{t('Your recently run queries will be displayed in the Recent ' + +'Queries panel below. Instead of building a new query, you can ' + +'reload a query that you\'ve recently run by clicking on the icon' + +' next to the query.', {ns: 'dataquery'})}

+

{t('Queries can be shared with others by clicking the icon. This will' + +' cause the query to be shared with all users who have access to the ' + +'fields used by the query. It will display in a Shared Queries panel ' + +'below the Recent Queries.', {ns: 'dataquery'})}

+

{t('You may also give a query a name at any time by clicking the icon.' + +' This makes it easier to find queries you care about by giving them an' + +' easier to remember name that can be used for filtering. When you ' + +'share a query, the name will be shared along with it.', + {ns: 'dataquery'})}

{studyQueriesParagraph}
+ label={t('Continue to Define Fields', {ns: 'dataquery'})} />
); } + export default Welcome; diff --git a/modules/dataquery/locale/dataquery.pot b/modules/dataquery/locale/dataquery.pot index 84b3f199537..36ce4e4c57b 100644 --- a/modules/dataquery/locale/dataquery.pot +++ b/modules/dataquery/locale/dataquery.pot @@ -24,11 +24,501 @@ msgstr "" msgid "Starred Queries" msgstr "" -msgid "Study Queries" +msgid "Query not yet run" msgstr "" +msgid "Loading data:" +msgstr "" + +msgid "{{value}} of {{max}} candidates" +msgstr "" + +msgid "Organizing headers:" +msgstr "" + +msgid "{{value}} of {{max}} columns" +msgstr "" + +msgid "Organizing data:" +msgstr "" + +msgid "Invalid progress type: {{type}}" +msgstr "" + +msgid "No data for visit" +msgstr "" + +msgid "Field Name" +msgstr "" + +msgid "Field Description" +msgstr "" + +msgid "Field Name: Field Description" +msgstr "" + +msgid "Rows (Cross-sectional)" +msgstr "" + +msgid "Columns (Longitudinal)" +msgstr "" + +msgid "Inline values (no download)" +msgstr "" + +msgid "Raw JSON (debugging only)" +msgstr "" + +msgid "Header Display Format" +msgstr "" + +msgid "Display visits as" +msgstr "" + +msgid "Label" +msgid_plural "Labels" +msgstr[0] "" +msgstr[1] "" + +msgid "Value" +msgid_plural "Values" +msgstr[0] "" +msgstr[1] "" + +msgid "Display options as" +msgstr "" + +msgid "Display empty visits?" +msgstr "" + +msgid "Row Number" +msgstr "" + +msgid "Modify Fields" +msgstr "" + +msgid "Choose Fields" +msgstr "" + +msgid "Modify Filters" +msgstr "" + +msgid "Add Filters" +msgstr "" + +msgid "Run Query" +msgstr "" + +msgid "Next Steps" +msgstr "" + +msgid "Delete item" +msgstr "" + +msgid "Delete Group" +msgstr "" + +msgid "Group does not have any items." +msgstr "" + +msgid "Group only has 1 item. A group with only 1 item is equivalent to not having the group." +msgstr "" + +msgid "Add \"{{operator}}\" condition to group" +msgstr "" + +msgid "New \"{{antiOperator}}\" subgroup" +msgstr "" + +msgid "Hide Advanced" +msgstr "" + +msgid "Show Advanced" +msgstr "" + +msgid "The \"nested groups\" options are advanced options for queries that do not have any specific condition at the base of the query. Use Add nested \"or\" condition groups if you need to build a query of the form. (a or b) and (c or d) [or (e and f)..]" +msgstr "" + +msgid "Add nested \"or\" condition groups" +msgstr "" + +msgid "Use Add nested \"and\" condition groups if you need to build a query of the form (a and b) or (c and d) [or (e and f)..]" +msgstr "" + +msgid "Add nested \"and\" condition groups" +msgstr "" + +msgid "Currently querying for ALL candidates." +msgstr "" + +msgid "You can add conditions by clicking one of the buttons below." +msgstr "" + +msgid "Click Add Condition to add one or more conditions to your filters (ie. \"Date Of Birth < 2015-02-15\"). This is most likely where you want to start your filters." +msgstr "" + +msgid "You can also import a population from a CSV by clicking the Import from CSV button." +msgstr "" + +msgid "The advanced options are for queries that do not have a condition to add at the base of the query." +msgstr "" + +msgid "Add Condition" +msgstr "" + +msgid "Import from CSV" +msgstr "" + +msgid "Use New \"and\" subgroup if the rest of the query you need to write is a subgroup consisting of \"and\" conditions. ie your query is of the form: (your condition above) or (c and d [and e and f..])" +msgstr "" + +msgid "New \"and\" subgroup" +msgstr "" + +msgid "Use New \"or\" subgroup if the rest of the query you need to write is a subgroup consisting of \"or\" conditions. ie your query is of the form: (your condition above) and (c or d [or e or f..])" +msgstr "" + +msgid "New \"or\" subgroup" +msgstr "" + +msgid "Currently querying for any candidates with:" +msgstr "" + +msgid "Add \"and\" condition" +msgstr "" + +msgid "Add \"or\" condition" +msgstr "" + +msgid "Delete Item" +msgstr "" + +msgid "Query matches {{count}} candidates" +msgstr "" + +msgid "Current Query" +msgstr "" + +msgid "Note that only candidates which you have permission to access in LORIS are included in results. Number of results may vary from other users running the same query." +msgstr "" + +msgid "Candidate matches" +msgstr "" + +msgid "Note: matches count only includes candidates that you have access to. Results may vary from other users due to permissions." +msgstr "" + +msgid "Select Visits" +msgstr "" + +msgid "Criteria" +msgstr "" + +msgid "Select an operator" +msgstr "" + +msgid "for at least one of the following visits" +msgstr "" + +msgid "This field may exist multiple times for a single {{scope}}. Adding a criteria based on it means that it must match for at least one of the data points." +msgstr "" + +msgid "This field may exist multiple times for a single {{scope}} and must match for *any* of the data points" +msgstr "" + +msgid "at visit {{visits}}" +msgstr "" + +msgid "Invalid field" +msgstr "" + +msgid "You must select a field for the criteria." +msgstr "" + +msgid "Invalid operator" +msgstr "" + +msgid "You must select an operator for the criteria." +msgstr "" + +msgid "Invalid value" +msgstr "" + +msgid "You must enter a value to compare the field against." +msgstr "" + +msgid "Invalid visits" +msgstr "" + +msgid "No visits selected for criteria." +msgstr "" + +msgid "Add criteria" +msgstr "" + +msgid "Field" +msgid_plural "Fields" +msgstr[0] "" +msgstr[1] "" + +msgid "Select a field" +msgstr "" + +msgid "in" +msgstr "" + +msgid "starts with" +msgstr "" + +msgid "contains" +msgstr "" + +msgid "ends with" +msgstr "" + +msgid "has data" +msgstr "" + +msgid "has no data" +msgstr "" + +msgid "exists" +msgstr "" + +msgid "does not exist" +msgstr "" + +msgid "number of" +msgstr "" + +msgid "Select a value" +msgstr "" + +msgid "Import Population From CSV" +msgstr "" + +msgid "CSV containing list of" +msgstr "" + +msgid "Candidate identifier type" +msgstr "" + +msgid "Does CSV contain a header line?" +msgstr "" + +msgid "CSV File" +msgstr "" + +msgid "Invalid CSV" +msgstr "" + +msgid "Could not parse CSV file" +msgstr "" + +msgid "No CSV Uploaded" +msgstr "" + +msgid "Please upload a CSV file before submitting." +msgstr "" + +msgid "Empty CSV" +msgstr "" + +msgid "The uploaded CSV file is empty." +msgstr "" + +msgid "Expected {{expectedLength}} columns in CSV. Got {{gotLength}} on line {{line}}." +msgstr "" + +msgid "Invalid DCCID" +msgstr "" + +msgid "Invalid DCCID ({{id}}) on line {{line}}." +msgstr "" + +msgid "Welcome to the Data Query Tool" +msgstr "" + +msgid "Study Query" +msgid_plural "Study Queries" +msgstr[0] "" +msgstr[1] "" + msgid "Candidate matches:" msgstr "" msgid "Note: matches count only includes candidates that you have access to. Results may vary from other users due to permissions." msgstr "" + +msgid "Instructions" +msgstr "" + +msgid "Recent Queries" +msgstr "" + +msgid "Shared Queries" +msgstr "" + +msgid "Starred Only" +msgstr "" + +msgid "Shared Only" +msgstr "" + +msgid "No run times (eliminate duplicates)" +msgstr "" + +msgid "Filter" +msgid_plural "Filters" +msgstr[0] "" +msgstr[1] "" + +msgid "Named Only" +msgstr "" + +msgid "Collapse queries" +msgstr "" + +msgid "(No filters for query)" +msgstr "" + +msgid "Page" +msgstr "" + +msgid "Unstar" +msgstr "" + +msgid "Star" +msgstr "" + +msgid "Unshare" +msgstr "" + +msgid "Share" +msgstr "" + +msgid "Query Loaded" +msgstr "" + +msgid "Successfully loaded query." +msgstr "" + +msgid "(Run at {{runTime}})" +msgstr "" + +msgid "You ran this query at {{runTime}}" +msgstr "" + +msgid "You ran this query" +msgstr "" + +msgid "(Shared by {{sharedBy}})" +msgstr "" + +msgid "Query shared by {{sharedBy}}" +msgstr "" + +msgid "Name Query" +msgstr "" + +msgid "Enter your query name" +msgstr "" + +msgid "Query name" +msgstr "" + +msgid "Must provide a query name." +msgstr "" + +msgid "Pin Top Query" +msgstr "" + +msgid "Pin Study Query" +msgstr "" + +msgid "Pin Dashboard Summary" +msgstr "" + +msgid "Pin To Login Page" +msgstr "" + +msgid "Must pin as study query, to dashboard, or to the login page." +msgstr "" + +msgid "Must provide a query name to pin query as." +msgstr "" + +msgid "{{columnName}} fields" +msgstr "" + +msgid "Define Filters" +msgstr "" + +msgid "Define Fields" +msgstr "" + +msgid "Reload query" +msgstr "" + +msgid "Continue to Define Fields" +msgstr "" + +msgid "Above, there is also a Study Queries panel. This are a special type of shared queries that have been pinned by a study administer to always display at the top of this page." +msgstr "" + +msgid "The data query tool allows you to query data within LORIS. There are three steps to defining a query:" +msgstr "" + +msgid "First, you must select the fields that you're interested in on the Define Fields page." +msgstr "" + +msgid "Next, you can optionally define filters on the Define Filters page to restrict the population that is returned." +msgstr "" + +msgid "Finally, you view your query results on the View Data page" +msgstr "" + +msgid "The Next Steps on the bottom right of your screen always the context-sensitive next steps that you can do to build your query." +msgstr "" + +msgid "Your recently run queries will be displayed in the Recent Queries panel below. Instead of building a new query, you can reload a query that you've recently run by clicking on the icon next to the query." +msgstr "" + +msgid "Queries can be shared with others by clicking the icon. This will cause the query to be shared with all users who have access to the fields used by the query. It will display in a Shared Queries panel below the Recent Queries." +msgstr "" + +msgid "You may also give a query a name at any time by clicking the icon. This makes it easier to find queries you care about by giving them an easier to remember name that can be used for filtering. When you share a query, the name will be shared along with it." +msgstr "" + +msgid "Selected Fields" +msgstr "" + +msgid "Available Fields" +msgstr "" + +msgid "Add all" +msgstr "" + +msgid "Remove all" +msgstr "" + +msgid "Filter within category" +msgstr "" + +msgid "Default Visits" +msgstr "" + +msgid "Sync with selected fields" +msgstr "" + +msgid "Select a field" +msgstr "" + +msgid "Select a category" +msgstr "" + +msgid "Invalid response" +msgstr "" + +msgid "Invalid tab" +msgstr "" diff --git a/modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po b/modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po new file mode 100644 index 00000000000..2b71dd5b87e --- /dev/null +++ b/modules/dataquery/locale/hi/LC_MESSAGES/dataquery.po @@ -0,0 +1,522 @@ +# Default LORIS strings to be translated (English). +# Copy this to a language specific file and add translations to the +# new file. +# Copyright (C) 2025 +# This file is distributed under the same license as the LORIS package. +# Dave MacFarlane , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: LORIS 27\n" +"Report-Msgid-Bugs-To: https://github.com/aces/Loris/issues\n" +"POT-Creation-Date: 2025-04-08 14:37-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: hi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "Data Query Tool (Beta)" +msgstr "डेटा क्वेरी टूल (बीटा)" + +msgid "Query not yet run" +msgstr "क्वेरी अभी तक नहीं चलाई गई" + +msgid "Loading data:" +msgstr "डेटा लोड हो रहा है:" + +msgid "{{value}} of {{max}} candidates" +msgstr "{{max}} में से {{value}} उम्मीदवार" + +msgid "Organizing headers:" +msgstr "हेडर व्यवस्थित किए जा रहे हैं:" + +msgid "{{value}} of {{max}} columns" +msgstr "{{max}} में से {{value}} कॉलम" + +msgid "Organizing data:" +msgstr "डेटा व्यवस्थित किया जा रहा है:" + +msgid "Invalid progress type: {{type}}" +msgstr "अमान्य प्रगति प्रकार: {{type}}" + +msgid "No data for visit" +msgstr "इस विज़िट के लिए कोई डेटा नहीं" + +msgid "Field Name" +msgstr "फ़ील्ड नाम" + +msgid "Field Description" +msgstr "फ़ील्ड विवरण" + +msgid "Field Name: Field Description" +msgstr "फ़ील्ड नाम: फ़ील्ड विवरण" + +msgid "Rows (Cross-sectional)" +msgstr "पंक्तियाँ (क्रॉस-सेक्शनल)" + +msgid "Columns (Longitudinal)" +msgstr "कॉलम (लॉन्गिट्यूडिनल)" + +msgid "Inline values (no download)" +msgstr "इनलाइन मान (कोई डाउनलोड नहीं)" + +msgid "Raw JSON (debugging only)" +msgstr "कच्चा JSON (सिर्फ डीबगिंग के लिए)" + +msgid "Header Display Format" +msgstr "हेडर प्रदर्शन प्रारूप" + +msgid "Display visits as" +msgstr "विज़िट प्रदर्शित करें इस प्रकार" + +msgid "Label" +msgid_plural "Labels" +msgstr[0] "लेबल" +msgstr[1] "लेबल्स" + +msgid "Value" +msgid_plural "Values" +msgstr[0] "कीमत" +msgstr[1] "मान" + +msgid "Display options as" +msgstr "विकल्प प्रदर्शित करें इस प्रकार" + +msgid "Display empty visits?" +msgstr "खाली विज़िट दिखाएँ?" + +msgid "Row Number" +msgstr "पंक्ति संख्या" + +msgid "Modify Fields" +msgstr "फ़ील्ड संशोधित करें" + +msgid "Choose Fields" +msgstr "फ़ील्ड चुनें" + +msgid "Modify Filters" +msgstr "फ़िल्टर संशोधित करें" + +msgid "Add Filters" +msgstr "फ़िल्टर जोड़ें" + +msgid "Run Query" +msgstr "क्वेरी चलाएँ" + +msgid "View Data" +msgstr "डेटा देखें" + +msgid "Next Steps" +msgstr "अगले चरण" + +msgid "Delete item" +msgstr "आइटम हटाएँ" + +msgid "Delete Group" +msgstr "समूह हटाएँ" + +msgid "Group does not have any items." +msgstr "समूह में कोई आइटम नहीं है।" + +msgid "Group only has 1 item. A group with only 1 item is equivalent to not having the group." +msgstr "समूह में केवल 1 आइटम है। केवल 1 आइटम वाला समूह होना, समूह न होने के बराबर है।" + +msgid "Add \"{{operator}}\" condition to group" +msgstr "समूह में \"{{operator}}\" शर्त जोड़ें" + +msgid "New \"{{antiOperator}}\" subgroup" +msgstr "नया \"{{antiOperator}}\" उपसमूह" + +msgid "Hide Advanced" +msgstr "उन्नत विकल्प छिपाएँ" + +msgid "Show Advanced" +msgstr "उन्नत विकल्प दिखाएँ" + +msgid "The \"nested groups\" options are advanced options for queries that do not have any specific condition at the base of the query. Use Add nested \"or\" condition groups if you need to build a query of the form. (a or b) and (c or d) [or (e and f)..]" +msgstr "\"नेस्टेड समूह\" विकल्प उन क्वेरीज़ के लिए उन्नत विकल्प हैं जिनके आधार में कोई विशिष्ट शर्त नहीं होती। नेस्टेड \"या\" शर्त समूह जोड़ें का उपयोग करें यदि आपको (a या b) और (c या d) [या (e और f)..] जैसी क्वेरी बनानी हो।" + +msgid "Add nested \"or\" condition groups" +msgstr "नेस्टेड \"या\" शर्त समूह जोड़ें" + +msgid "Use Add nested \"and\" condition groups if you need to build a query of the form (a and b) or (c and d) [or (e and f)..]" +msgstr "यदि आपको (a और b) या (c और d) [या (e और f)..] जैसी क्वेरी बनानी है तो नेस्टेड \"और\" शर्त समूह जोड़ें का उपयोग करें।" + +msgid "Add nested \"and\" condition groups" +msgstr "नेस्टेड \"और\" शर्त समूह जोड़ें" + +msgid "Currently querying for ALL candidates." +msgstr "वर्तमान में सभी उम्मीदवारों के लिए क्वेरी की जा रही है।" + +msgid "You can add conditions by clicking one of the buttons below." +msgstr "आप नीचे दिए गए बटनों में से किसी पर क्लिक करके शर्तें जोड़ सकते हैं।" + +msgid "Click Add Condition to add one or more conditions to your filters (ie. \"Date Of Birth < 2015-02-15\"). This is most likely where you want to start your filters." +msgstr "फ़िल्टर में एक या अधिक शर्तें जोड़ने के लिए 'शर्त जोड़ें' पर क्लिक करें (जैसे \"जन्म तिथि < 2015-02-15\")। संभवतः यही वह जगह है जहाँ से आप अपने फ़िल्टर शुरू करना चाहेंगे।" + +msgid "You can also import a population from a CSV by clicking the Import from CSV button." +msgstr "आप 'CSV से आयात करें' बटन पर क्लिक करके CSV से जनसंख्या भी आयात कर सकते हैं।" + +msgid "The advanced options are for queries that do not have a condition to add at the base of the query." +msgstr "उन्नत विकल्प उन क्वेरीज़ के लिए हैं जिनके आधार में कोई शर्त जोड़ने के लिए नहीं है।" + +msgid "Add Condition" +msgstr "शर्त जोड़ें" + +msgid "Import from CSV" +msgstr "CSV से आयात करें" + +msgid "Use New \"and\" subgroup if the rest of the query you need to write is a subgroup consisting of \"and\" conditions. ie your query is of the form: (your condition above) or (c and d [and e and f..])" +msgstr "नया \"and\" उपसमूह उपयोग करें यदि बाकी क्वेरी \"and\" शर्तों वाले उपसमूह से बनी हो। जैसे: (ऊपर की शर्त) या (c और d [और e और f..])।" + +msgid "New \"and\" subgroup" +msgstr "नया \"and\" उपसमूह" + +msgid "Use New \"or\" subgroup if the rest of the query you need to write is a subgroup consisting of \"or\" conditions. ie your query is of the form: (your condition above) and (c or d [or e or f..])" +msgstr "नया \"or\" उपसमूह उपयोग करें यदि बाकी क्वेरी \"or\" शर्तों वाले उपसमूह से बनी हो। जैसे: (ऊपर की शर्त) और (c या d [या e या f..])।" + +msgid "New \"or\" subgroup" +msgstr "नया \"or\" उपसमूह" + +msgid "Currently querying for any candidates with:" +msgstr "वर्तमान में ऐसे किसी भी उम्मीदवार के लिए क्वेरी की जा रही है जिनमें हो:" + +msgid "Add \"and\" condition" +msgstr "\"and\" शर्त जोड़ें" + +msgid "Add \"or\" condition" +msgstr "\"or\" शर्त जोड़ें" + +msgid "Delete Item" +msgstr "आइटम हटाएँ" + +msgid "Query matches {{count}} candidates" +msgstr "क्वेरी {{count}} उम्मीदवारों से मेल खाती है" + +msgid "Current Query" +msgstr "वर्तमान क्वेरी" + +msgid "Note that only candidates which you have permission to access in LORIS are included in results. Number of results may vary from other users running the same query." +msgstr "ध्यान दें कि केवल वे उम्मीदवार शामिल हैं जिन्हें LORIS में एक्सेस करने की आपको अनुमति है। परिणामों की संख्या अन्य उपयोगकर्ताओं से भिन्न हो सकती है।" + +msgid "Candidate matches" +msgstr "उम्मीदवार मेल खाते हैं" + +msgid "Note: matches count only includes candidates that you have access to. Results may vary from other users due to permissions." +msgstr "नोट: मेल गिनती केवल उन्हीं उम्मीदवारों को शामिल करती है जिन तक आपकी पहुँच है। अनुमतियों के कारण परिणाम अन्य उपयोगकर्ताओं से भिन्न हो सकते हैं।" + +msgid "Select Visits" +msgstr "विज़िट चुनें" + +msgid "Criteria" +msgstr "मानदंड" + +msgid "Select an operator" +msgstr "ऑपरेटर चुनें" + +msgid "for at least one of the following visits" +msgstr "निम्नलिखित में से कम से कम एक विज़िट के लिए" + +msgid "This field may exist multiple times for a single {{scope}}. Adding a criteria based on it means that it must match for at least one of the data points." +msgstr "यह फ़ील्ड एक {{scope}} के लिए कई बार हो सकती है। इस पर आधारित मानदंड जोड़ने का मतलब है कि यह कम से कम एक डेटा बिंदु के लिए मेल खाना चाहिए।" + +msgid "This field may exist multiple times for a single {{scope}} and must match for *any* of the data points" +msgstr "यह फ़ील्ड एक ही {{scope}} के लिए कई बार मौजूद हो सकती है और *किसी भी* डेटा पॉइंट से मैच होनी चाहिए" + +msgid "at visit {{visits}}" +msgstr "विज़िट {{visits}} पर" + +msgid "or" +msgstr "वा" + +msgid "Invalid field" +msgstr "अमान्य फ़ील्ड" + +msgid "You must select a field for the criteria." +msgstr "आपको मानदंड के लिए एक फ़ील्ड चुननी होगी।" + +msgid "Invalid operator" +msgstr "अमान्य ऑपरेटर" + +msgid "You must select an operator for the criteria." +msgstr "आपको मानदंड के लिए एक ऑपरेटर चुनना होगा।" + +msgid "Invalid value" +msgstr "अमान्य मान" + +msgid "You must enter a value to compare the field against." +msgstr "फ़ील्ड की तुलना करने के लिए आपको एक मान दर्ज करना होगा।" + +msgid "Invalid visits" +msgstr "अमान्य विज़िट" + +msgid "No visits selected for criteria." +msgstr "मानदंड के लिए कोई विज़िट चयनित नहीं है।" + +msgid "Add criteria" +msgstr "मानदंड जोड़ें" + +msgid "Field" +msgid_plural "Fields" +msgstr[0] "फ़ील्ड" +msgstr[1] "फ़ील्ड्स" + +msgid "Select a field" +msgstr "फ़ील्ड चुनें" + +msgid "in" +msgstr "में" + +msgid "starts with" +msgstr "से शुरू होता है" + +msgid "contains" +msgstr "शामिल है" + +msgid "ends with" +msgstr "से समाप्त होता है" + +msgid "has data" +msgstr "डेटा है" + +msgid "has no data" +msgstr "कोई डेटा नहीं है" + +msgid "exists" +msgstr "मौजूद है" + +msgid "does not exist" +msgstr "मौजूद नहीं है" + +msgid "number of" +msgstr "संख्या" + +msgid "true" +msgstr "सही" + +msgid "false" +msgstr "ग़लत" + +msgid "Select a value" +msgstr "मान चुनें" + +msgid "Import Population From CSV" +msgstr "CSV से जनसंख्या आयात करें" + +msgid "CSV containing list of" +msgstr "CSV जिसमें सूची है" + +msgid "Candidate identifier type" +msgstr "उम्मीदवार पहचानकर्ता प्रकार" + +msgid "Does CSV contain a header line?" +msgstr "क्या CSV में हेडर पंक्ति है?" + +msgid "CSV File" +msgstr "CSV फ़ाइल" + +msgid "Invalid CSV" +msgstr "अमान्य CSV" + +msgid "Could not parse CSV file" +msgstr "CSV फ़ाइल पार्स नहीं की जा सकी" + +msgid "No CSV Uploaded" +msgstr "कोई CSV अपलोड नहीं किया गया" + +msgid "Please upload a CSV file before submitting." +msgstr "सबमिट करने से पहले कृपया एक CSV फ़ाइल अपलोड करें।" + +msgid "Empty CSV" +msgstr "खाली CSV" + +msgid "The uploaded CSV file is empty." +msgstr "अपलोड की गई CSV फ़ाइल खाली है।" + +msgid "Expected {{expectedLength}} columns in CSV. Got {{gotLength}} on line {{line}}." +msgstr "CSV में {{expectedLength}} कॉलम अपेक्षित थे। पंक्ति {{line}} पर {{gotLength}} मिले।" + +msgid "Invalid DCC ID" +msgstr "अमान्य DCC ID" + +msgid "Invalid DCC ID ({{id}}) on line {{line}}." +msgstr "अमान्य DCC ID ({{id}}) पंक्ति {{line}} पर।" + +msgid "Welcome to the Data Query Tool" +msgstr "डेटा क्वेरी टूल में आपका स्वागत है" + +msgid "Study Query" +msgid_plural "Study Queries" +msgstr[0] "अध्ययन क्वेरीज़" +msgstr[1] "अध्ययन संबंधी प्रश्न" + +msgid "Instructions" +msgstr "निर्देश" + +msgid "Recent Queries" +msgstr "हाल की क्वेरीज़" + +msgid "Shared Queries" +msgstr "साझा की गई क्वेरीज़" + +msgid "Starred Only" +msgstr "केवल स्टार की गई" + +msgid "Shared Only" +msgstr "केवल साझा की गई" + +msgid "No run times (eliminate duplicates)" +msgstr "कोई रन समय नहीं (डुप्लीकेट हटाएँ)" + +msgid "Filter" +msgid_plural "Filters" +msgstr[0] "फ़िल्टर" +msgstr[1] "फिल्टर" + +msgid "Named Only" +msgstr "केवल नाम वाली" + +msgid "Collapse queries" +msgstr "क्वेरीज़ समेटें" + +msgid "(No filters for query)" +msgstr "(क्वेरी के लिए कोई फ़िल्टर नहीं)" + +msgid "Page" +msgstr "पृष्ठ" + +msgid "Unstar" +msgstr "स्टार हटाएँ" + +msgid "Star" +msgstr "स्टार करें" + +msgid "Unshare" +msgstr "साझा हटाएँ" + +msgid "Share" +msgstr "साझा करें" + +msgid "Query Loaded" +msgstr "क्वेरी लोड हो गई" + +msgid "Successfully loaded query." +msgstr "क्वेरी सफलतापूर्वक लोड हो गई।" + +msgid "(Run at {{runTime}})" +msgstr "(चलाया गया {{runTime}} पर)" + +msgid "You ran this query at {{runTime}}" +msgstr "आपने यह क्वेरी {{runTime}} पर चलाई" + +msgid "You ran this query" +msgstr "आपने यह क्वेरी चलाई" + +msgid "(Shared by {{sharedBy}})" +msgstr "(साझा किया गया {{sharedBy}} द्वारा)" + +msgid "Query shared by {{sharedBy}}" +msgstr "क्वेरी {{sharedBy}} द्वारा साझा की गई" + +msgid "Name Query" +msgstr "नाम क्वेरी" + +msgid "Enter your query name" +msgstr "अपनी क्वेरी का नाम डालें" + +msgid "Query name" +msgstr "क्वेरी का नाम" + +msgid "Must provide a query name." +msgstr "क्वेरी का नाम देना होगा।" + +msgid "Pin Top Query" +msgstr "पिन टॉप क्वेरी" + +msgid "Pin Study Query" +msgstr "पिन अध्ययन क्वेरी" + +msgid "Pin Dashboard Summary" +msgstr "पिन डैशबोर्ड सारांश" + +msgid "Pin To Login Page" +msgstr "लॉगिन पेज पर पिन करें" + +msgid "Must pin as study query, to dashboard, or to the login page." +msgstr "स्टडी क्वेरी के तौर पर, डैशबोर्ड पर, या लॉगिन पेज पर पिन करना होगा।" + +msgid "Must provide a query name to pin query as." +msgstr "क्वेरी को पिन करने के लिए क्वेरी नाम देना होगा।" + +msgid "Define Filters" +msgstr "फ़िल्टर निर्धारित करें" + +msgid "Define Fields" +msgstr "फ़ील्ड निर्धारित करें" + +msgid "Reload query" +msgstr "क्वेरी पुनः लोड करें" + +msgid "Continue to Define Fields" +msgstr "फ़ील्ड परिभाषित करना जारी रखें" + +msgid "Above, there is also a Study Queries panel. This are a special type of shared queries that have been pinned by a study administer to always display at the top of this page." +msgstr "ऊपर एक अध्ययन क्वेरी पैनल भी है। यह साझा क्वेरी का एक विशेष प्रकार है जिसे अध्ययन प्रशासक द्वारा इस पृष्ठ के शीर्ष पर हमेशा प्रदर्शित होने के लिए पिन किया गया है।" + +msgid "The data query tool allows you to query data within LORIS. There are three steps to defining a query:" +msgstr "डेटा क्वेरी टूल आपको LORIS के भीतर डेटा क्वेरी करने की अनुमति देता है। क्वेरी को परिभाषित करने के तीन चरण हैं:" + +msgid "First, you must select the fields that you're interested in on the Define Fields page." +msgstr "पहले, आपको 'फ़ील्ड परिभाषित करें' पृष्ठ पर वे फ़ील्ड चुननी होंगी जिनमें आपकी रुचि है।" + +msgid "Next, you can optionally define filters on the Define Filters page to restrict the population that is returned." +msgstr "इसके बाद, आप वैकल्पिक रूप से 'फ़िल्टर परिभाषित करें' पृष्ठ पर फ़िल्टर परिभाषित कर सकते हैं ताकि लौटाई गई जनसंख्या को सीमित किया जा सके।" + +msgid "Finally, you view your query results on the View Data page" +msgstr "अंत में, आप 'डेटा देखें' पृष्ठ पर अपनी क्वेरी के परिणाम देख सकते हैं।" + +msgid "The Next Steps on the bottom right of your screen always the context-sensitive next steps that you can do to build your query." +msgstr "आपकी स्क्रीन के नीचे दाईं ओर स्थित 'अगले चरण' हमेशा संदर्भ-संवेदनशील चरण दिखाते हैं जो आप अपनी क्वेरी बनाने के लिए कर सकते हैं।" + +msgid "Your recently run queries will be displayed in the Recent Queries panel below. Instead of building a new query, you can reload a query that you've recently run by clicking on the icon next to the query." +msgstr "आपकी हाल ही में चलाई गई क्वेरी नीचे 'हाल की क्वेरी' पैनल में दिखाई देंगी। नई क्वेरी बनाने के बजाय, आप क्वेरी के बगल में स्थित आइकन पर क्लिक करके हाल ही में चलाई गई क्वेरी को फिर से लोड कर सकते हैं।" + +msgid "Queries can be shared with others by clicking the icon. This will cause the query to be shared with all users who have access to the fields used by the query. It will display in a Shared Queries panel below the Recent Queries." +msgstr "क्वेरी को आइकन पर क्लिक करके दूसरों के साथ साझा किया जा सकता है। इससे क्वेरी उन सभी उपयोगकर्ताओं के साथ साझा हो जाएगी जिन्हें उस क्वेरी में उपयोग किए गए फ़ील्ड तक पहुँच है। यह 'हाल की क्वेरी' के नीचे 'साझा क्वेरी' पैनल में दिखाई देगी।" + +msgid "You may also give a query a name at any time by clicking the icon. This makes it easier to find queries you care about by giving them an easier to remember name that can be used for filtering. When you share a query, the name will be shared along with it." +msgstr "आप किसी भी समय आइकन पर क्लिक करके क्वेरी को एक नाम भी दे सकते हैं। इससे उन क्वेरियों को ढूंढना आसान हो जाता है जिनकी आपको परवाह है, क्योंकि आप उन्हें याद रखने में आसान नाम दे सकते हैं जिसका उपयोग फ़िल्टर करने के लिए किया जा सकता है। जब आप क्वेरी साझा करते हैं, तो उसका नाम भी उसके साथ साझा किया जाएगा।" + +msgid "Selected Fields" +msgstr "चयनित फ़ील्ड" + +msgid "Available Fields" +msgstr "उपलब्ध फ़ील्ड" + +msgid "Add all" +msgstr "सभी जोड़ें" + +msgid "Remove all" +msgstr "सभी हटाएँ" + +msgid "Filter within category" +msgstr "श्रेणी के भीतर फ़िल्टर करें" + +msgid "Default Visits" +msgstr "डिफ़ॉल्ट विज़िट" + +msgid "Sync with selected fields" +msgstr "चयनित फ़ील्ड के साथ समन्वय करें" + +msgid "Select a category" +msgstr "एक फ़ील्ड चुनें" + +msgid "Invalid response" +msgstr "अमान्य उत्तर" + +msgid "Invalid tab" +msgstr "अमान्य टैब" + diff --git a/tsconfig.json b/tsconfig.json index 68d36de7035..7eb9ae80924 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ "lib": [ "es6", "dom", "es2017" ], - "strict" : true + "strict" : true, } }