@openmrs/esm-form-builder-app 3.1.1-pre.2178 → 3.1.1-pre.2185

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,9 +188,9 @@
188
188
  "initial": false,
189
189
  "entry": false,
190
190
  "recorded": false,
191
- "size": 707267,
191
+ "size": 709847,
192
192
  "sizes": {
193
- "javascript": 707267
193
+ "javascript": 709847
194
194
  },
195
195
  "names": [],
196
196
  "idHints": [],
@@ -204,7 +204,7 @@
204
204
  "auxiliaryFiles": [
205
205
  "1208.js.map"
206
206
  ],
207
- "hash": "70d3ba3c975d31e9",
207
+ "hash": "561f892439c8a5d1",
208
208
  "childrenByOrder": {}
209
209
  },
210
210
  {
@@ -617,9 +617,9 @@
617
617
  "initial": false,
618
618
  "entry": false,
619
619
  "recorded": false,
620
- "size": 13398,
620
+ "size": 13468,
621
621
  "sizes": {
622
- "javascript": 13398
622
+ "javascript": 13468
623
623
  },
624
624
  "names": [],
625
625
  "idHints": [],
@@ -631,7 +631,7 @@
631
631
  "4300.js"
632
632
  ],
633
633
  "auxiliaryFiles": [],
634
- "hash": "5c3b7c8e158103a2",
634
+ "hash": "7b1f86f0827b1db8",
635
635
  "childrenByOrder": {}
636
636
  },
637
637
  {
@@ -1797,7 +1797,7 @@
1797
1797
  "auxiliaryFiles": [
1798
1798
  "8091.js.map"
1799
1799
  ],
1800
- "hash": "9589730ab86032cb",
1800
+ "hash": "bb301aa5f424eb42",
1801
1801
  "childrenByOrder": {}
1802
1802
  },
1803
1803
  {
@@ -1987,7 +1987,7 @@
1987
1987
  "auxiliaryFiles": [
1988
1988
  "main.js.map"
1989
1989
  ],
1990
- "hash": "3c7c8be6655dd104",
1990
+ "hash": "b08048e79f21c17b",
1991
1991
  "childrenByOrder": {}
1992
1992
  },
1993
1993
  {
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"add-form-reference-modal","component":"addFormReferenceModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"},{"name":"edit-translation-modal","component":"editTranslationModal"}],"version":"3.1.1-pre.2178"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"add-form-reference-modal","component":"addFormReferenceModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"},{"name":"edit-translation-modal","component":"editTranslationModal"}],"version":"3.1.1-pre.2185"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-builder-app",
3
- "version": "3.1.1-pre.2178",
3
+ "version": "3.1.1-pre.2185",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Form Builder for O3",
6
6
  "browser": "dist/openmrs-esm-form-builder-app.js",
@@ -19,28 +19,26 @@ const TranslationBuilder: React.FC<TranslationBuilderProps> = ({ formSchema, onU
19
19
  const [translations, setTranslations] = useState<Record<string, string>>({});
20
20
  const [loading, setLoading] = useState(false);
21
21
  const [error, setError] = useState<string | null>(null);
22
+ const [downloadError, setDownloadError] = useState<string | null>(null);
22
23
  const [activeTab, setActiveTab] = useState<'all' | 'translated' | 'untranslated'>('all');
23
24
 
24
25
  const langCode = selectedLanguageCode;
25
26
 
27
+ const fallbackStrings = useMemo(() => {
28
+ return formSchema ? extractTranslatableStrings(formSchema) : {};
29
+ }, [formSchema]);
30
+
26
31
  useEffect(() => {
27
32
  if (!formSchema) return;
28
33
  const translationsMap = formSchema.translations as Record<string, Record<string, string>> | undefined;
29
-
30
34
  const schemaTranslations = translationsMap?.[langCode];
31
- if (schemaTranslations) {
32
- setTranslations(schemaTranslations);
33
- } else {
34
- const fallbackStrings = extractTranslatableStrings(formSchema);
35
- setTranslations(fallbackStrings);
36
- }
37
- }, [formSchema, langCode]);
35
+ setTranslations(schemaTranslations ?? fallbackStrings);
36
+ }, [formSchema, langCode, fallbackStrings]);
38
37
 
39
38
  const handleUpdateValue = useCallback(
40
39
  (key: string, newValue: string) => {
41
40
  const updatedTranslations = { ...translations, [key]: newValue };
42
41
  setTranslations(updatedTranslations);
43
-
44
42
  if (formSchema) {
45
43
  const updatedSchema = { ...formSchema };
46
44
  if (!updatedSchema.translations) {
@@ -70,25 +68,61 @@ const TranslationBuilder: React.FC<TranslationBuilderProps> = ({ formSchema, onU
70
68
  [translations, handleUpdateValue],
71
69
  );
72
70
 
73
- const fallbackStrings: Record<string, string> = useMemo(() => {
74
- return formSchema ? extractTranslatableStrings(formSchema) : {};
75
- }, [formSchema]);
76
-
77
- const isTranslated: (key: string, value: string | undefined | null) => boolean = (key, value) => {
71
+ const downloadableTranslationResource = useMemo(() => {
72
+ if (!formSchema) return null;
73
+ const schemaTranslations = formSchema.translations?.[langCode];
74
+ const translationsToExport = langCode === 'en' ? fallbackStrings : schemaTranslations;
75
+
76
+ if (!translationsToExport) return null;
77
+
78
+ return new Blob(
79
+ [
80
+ JSON.stringify(
81
+ {
82
+ uuid: formSchema.uuid || '',
83
+ form: formSchema.name,
84
+ description: `${langCode.toUpperCase()} Translations for '${formSchema.name}'`,
85
+ language: langCode,
86
+ translations: translationsToExport,
87
+ },
88
+ null,
89
+ 2,
90
+ ),
91
+ ],
92
+ { type: 'application/json' },
93
+ );
94
+ }, [formSchema, langCode, fallbackStrings]);
95
+
96
+ const isTranslated = (key: string, value: string | undefined | null): boolean => {
78
97
  const fallback = fallbackStrings[key] ?? '';
79
98
  return value != null && value.trim() !== '' && value.trim() !== fallback.trim();
80
99
  };
81
100
 
82
- const filteredTranslations: Array<[string, string]> = Object.entries(translations).filter(([key, value]) => {
83
- if (activeTab === 'translated') {
84
- return isTranslated(key, value);
85
- }
86
- if (activeTab === 'untranslated') {
87
- return !isTranslated(key, value);
88
- }
101
+ const filteredTranslations = Object.entries(translations).filter(([key, value]) => {
102
+ if (activeTab === 'translated') return isTranslated(key, value);
103
+ if (activeTab === 'untranslated') return !isTranslated(key, value);
89
104
  return true;
90
105
  });
91
106
 
107
+ const handleDownloadTranslation = useCallback(() => {
108
+ setDownloadError(null);
109
+ if (!downloadableTranslationResource) {
110
+ if (langCode !== 'en') {
111
+ setDownloadError(t('noTranslationForLang', 'No translations found for selected language.'));
112
+ }
113
+ return;
114
+ }
115
+
116
+ const url = URL.createObjectURL(downloadableTranslationResource);
117
+ const link = document.createElement('a');
118
+ link.href = url;
119
+ link.download = `${formSchema?.name}_translations_${langCode}.json`;
120
+ document.body.appendChild(link);
121
+ link.click();
122
+ document.body.removeChild(link);
123
+ URL.revokeObjectURL(url);
124
+ }, [downloadableTranslationResource, langCode, formSchema?.name, t]);
125
+
92
126
  return (
93
127
  <div className={styles.translationBuilderContainer}>
94
128
  <div className={styles.translationBuilderHeader}>
@@ -124,7 +158,12 @@ const TranslationBuilder: React.FC<TranslationBuilderProps> = ({ formSchema, onU
124
158
  }}
125
159
  />
126
160
 
127
- <IconButton kind="ghost" label={t('downloadTranslation', 'Download translation')} size="md">
161
+ <IconButton
162
+ kind="ghost"
163
+ label={t('downloadTranslation', 'Download translation')}
164
+ size="md"
165
+ onClick={handleDownloadTranslation}
166
+ >
128
167
  <Download />
129
168
  </IconButton>
130
169
  </div>
@@ -135,42 +174,53 @@ const TranslationBuilder: React.FC<TranslationBuilderProps> = ({ formSchema, onU
135
174
  ) : error ? (
136
175
  <InlineNotification kind="error" title={t('error', 'Error')} subtitle={error} lowContrast />
137
176
  ) : (
138
- <div className={styles.translationEditor}>
139
- {filteredTranslations.length > 0 ? (
140
- filteredTranslations.map(([key, value]) => (
141
- <div key={key} className={styles.translationRow}>
142
- <div className={styles.translationKey}>{key}</div>
143
- <div className={styles.translatedKey}>{value}</div>
144
- <div className={styles.inlineControls}>
145
- <IconButton
146
- kind="ghost"
147
- label={t('editString', 'Edit string')}
148
- onClick={() => {
149
- handleEditClick(key);
150
- }}
151
- size="md"
152
- className={styles.deleteButton}
153
- >
154
- <Edit />
155
- </IconButton>
156
- </div>
157
- </div>
158
- ))
159
- ) : (
177
+ <>
178
+ {downloadError && (
160
179
  <InlineNotification
161
- kind="info"
162
- subtitle={
163
- activeTab === 'translated'
164
- ? t('noTranslatedStrings', 'No strings are translated yet.')
165
- : activeTab === 'untranslated'
166
- ? t('noUntranslatedStrings', 'All strings are translated.')
167
- : t('noTranslations', 'No translatable strings found.')
168
- }
169
- hideCloseButton
180
+ className={styles.downloadError}
181
+ kind="error"
182
+ title={t('error', 'Error')}
183
+ subtitle={downloadError}
170
184
  lowContrast
185
+ onClose={() => setDownloadError(null)}
171
186
  />
172
187
  )}
173
- </div>
188
+
189
+ <div className={styles.translationEditor}>
190
+ {filteredTranslations.length > 0 ? (
191
+ filteredTranslations.map(([key, value]) => (
192
+ <div key={key} className={styles.translationRow}>
193
+ <div className={styles.translationKey}>{key}</div>
194
+ <div className={styles.translatedKey}>{value}</div>
195
+ <div className={styles.inlineControls}>
196
+ <IconButton
197
+ kind="ghost"
198
+ label={t('editString', 'Edit string')}
199
+ onClick={() => handleEditClick(key)}
200
+ size="md"
201
+ className={styles.deleteButton}
202
+ >
203
+ <Edit />
204
+ </IconButton>
205
+ </div>
206
+ </div>
207
+ ))
208
+ ) : (
209
+ <InlineNotification
210
+ kind="info"
211
+ subtitle={
212
+ activeTab === 'translated'
213
+ ? t('noTranslatedStrings', 'No strings are translated yet.')
214
+ : activeTab === 'untranslated'
215
+ ? t('noUntranslatedStrings', 'All strings are translated.')
216
+ : t('noTranslations', 'No translatable strings found.')
217
+ }
218
+ hideCloseButton
219
+ lowContrast
220
+ />
221
+ )}
222
+ </div>
223
+ </>
174
224
  )}
175
225
  </div>
176
226
  );
@@ -94,3 +94,7 @@
94
94
  font-weight: 600;
95
95
  }
96
96
  }
97
+
98
+ .downloadError {
99
+ margin-top: layout.$spacing-05;
100
+ }