dmartincy commited on
Commit
8304957
·
1 Parent(s): 231b1ef

Translation algorithm improvements

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. document-authoring.js +134 -14
  3. index.html +0 -42
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: purple
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
- short_description: Document authoring and translation to English
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
+ short_description: A WYSIWYG editor that translates text to multiple languages.
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
document-authoring.js CHANGED
@@ -2,9 +2,9 @@
2
  const app = document.getElementById('app');
3
  app.innerHTML = `
4
  <div style="height: 100vh; display: flex; flex-direction: column;">
5
- <div style="padding: 20px;">
6
  <div id="statusIndicator">
7
- <span class="spinner"></span> Services starting...
8
  </div>
9
  <div id="translationControls">
10
  <select id="languageSelect">
@@ -15,10 +15,59 @@ app.innerHTML = `
15
  <button id="translateButton">Translate Document</button>
16
  </div>
17
  </div>
18
- <div id="editor" style="flex: 1; border: 1px solid black; min-height: 0;"></div>
 
 
19
  </div>
20
  `;
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  // Load Document Authoring SDK
23
  const script = document.createElement('script');
24
  script.src = 'https://document-authoring-cdn.pspdfkit.com/releases/document-authoring-1.0.26-umd.js';
@@ -35,7 +84,42 @@ script.onload = async () => {
35
  }
36
  );
37
 
38
- // Translation function with language parameter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  async function translate(content, lang) {
40
  try {
41
  const response = await fetch('/client/api/v1/chat/completions', {
@@ -47,11 +131,11 @@ script.onload = async () => {
47
  messages: [
48
  {
49
  role: "system",
50
- content: "You are a translator. Only provide the translation, no explanations or additional text."
51
  },
52
  {
53
  role: "user",
54
- content: `Translate the following text to ${lang}: ${content}`
55
  }
56
  ],
57
  model: "gemma-2b",
@@ -71,14 +155,50 @@ script.onload = async () => {
71
  }
72
  }
73
 
74
- // Function to recursively translate text in the JSON structure
75
- async function translateRecursive(obj, lang) {
76
- for (let key in obj) {
77
- if (typeof obj[key] === "string" && key === "text" && obj[key].length > 0) {
78
- obj[key] = await translate(obj[key], lang);
79
- } else if (typeof obj[key] === "object") {
80
- await translateRecursive(obj[key], lang);
81
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
  }
84
 
 
2
  const app = document.getElementById('app');
3
  app.innerHTML = `
4
  <div style="height: 100vh; display: flex; flex-direction: column;">
5
+ <div style="background: #f5f5f5; border-bottom: 1px solid #ddd; padding: 10px;">
6
  <div id="statusIndicator">
7
+ <span class="spinner"></span> Translation services starting...
8
  </div>
9
  <div id="translationControls">
10
  <select id="languageSelect">
 
15
  <button id="translateButton">Translate Document</button>
16
  </div>
17
  </div>
18
+ <div style="flex: 1; display: flex; flex-direction: column; position: relative;">
19
+ <div id="editor" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
20
+ </div>
21
  </div>
22
  `;
23
 
24
+ // Add retry count
25
+ let retryCount = 0;
26
+ const MAX_RETRIES = 10; // Will try for 20 seconds (10 attempts * 2 second interval)
27
+
28
+ function checkServicesStatus() {
29
+ fetch('/healthcheck')
30
+ .then(response => {
31
+ if (response.ok) {
32
+ window.servicesReady = true;
33
+ const translationControls = document.getElementById('translationControls');
34
+ const statusIndicator = document.getElementById('statusIndicator');
35
+ if (translationControls) {
36
+ translationControls.style.display = 'block';
37
+ }
38
+ if (statusIndicator) {
39
+ statusIndicator.style.display = 'none';
40
+ }
41
+ return true;
42
+ }
43
+ throw new Error('Services not ready');
44
+ })
45
+ .catch(error => {
46
+ retryCount++;
47
+ const statusIndicator = document.getElementById('statusIndicator');
48
+ if (statusIndicator) {
49
+ if (retryCount >= MAX_RETRIES) {
50
+ statusIndicator.innerHTML = '❌ Failed to initialize translation services. Try restarting the space.';
51
+ statusIndicator.style.color = '#dc3545';
52
+ clearInterval(statusInterval);
53
+ } else {
54
+ statusIndicator.innerHTML = `<span class="spinner"></span> Translation services starting... (attempt ${retryCount}/${MAX_RETRIES})`;
55
+ }
56
+ }
57
+ console.log('Waiting for services...', error);
58
+ return false;
59
+ });
60
+ }
61
+
62
+ // Check status every 2 seconds until ready or max retries reached
63
+ const statusInterval = setInterval(() => {
64
+ if (window.servicesReady || retryCount >= MAX_RETRIES) {
65
+ clearInterval(statusInterval);
66
+ } else {
67
+ checkServicesStatus();
68
+ }
69
+ }, 2000);
70
+
71
  // Load Document Authoring SDK
72
  const script = document.createElement('script');
73
  script.src = 'https://document-authoring-cdn.pspdfkit.com/releases/document-authoring-1.0.26-umd.js';
 
84
  }
85
  );
86
 
87
+ // Helper function to extract text runs with their paths
88
+ function flattenTextRuns(obj, path = [], result = []) {
89
+ for (let key in obj) {
90
+ const newPath = [...path, key];
91
+ if (typeof obj[key] === "string" && key === "text" && obj[key].length > 0) {
92
+ result.push({ text: obj[key], path: newPath });
93
+ } else if (typeof obj[key] === "object") {
94
+ flattenTextRuns(obj[key], newPath, result);
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+
100
+ // Helper function to group adjacent text runs
101
+ function groupAdjacentRuns(textRuns, maxChunkSize = 1000) {
102
+ const groups = [];
103
+ let currentGroup = [];
104
+ let currentSize = 0;
105
+
106
+ for (let run of textRuns) {
107
+ if (currentSize + run.text.length > maxChunkSize && currentGroup.length > 0) {
108
+ groups.push([...currentGroup]);
109
+ currentGroup = [];
110
+ currentSize = 0;
111
+ }
112
+ currentGroup.push(run);
113
+ currentSize += run.text.length;
114
+ }
115
+
116
+ if (currentGroup.length > 0) {
117
+ groups.push(currentGroup);
118
+ }
119
+
120
+ return groups;
121
+ }
122
+
123
  async function translate(content, lang) {
124
  try {
125
  const response = await fetch('/client/api/v1/chat/completions', {
 
131
  messages: [
132
  {
133
  role: "system",
134
+ content: `Translate the following text to ${lang}. Each segment is marked with ||| delimiters. Translate each segment independently while preserving original capitalization and punctuation. Add spaces at the start or end of segments only if they exist in the original text.`
135
  },
136
  {
137
  role: "user",
138
+ content: content
139
  }
140
  ],
141
  model: "gemma-2b",
 
155
  }
156
  }
157
 
158
+ // Function to update nested object value by path
159
+ function setNestedValue(obj, path, value) {
160
+ let current = obj;
161
+ for (let i = 0; i < path.length - 1; i++) {
162
+ current = current[path[i]];
163
+ }
164
+ current[path[path.length - 1]] = value;
165
+ }
166
+
167
+ // Enhanced recursive translation function
168
+ async function translateRecursive(docJson, lang) {
169
+ const textRuns = flattenTextRuns(docJson);
170
+ const groups = groupAdjacentRuns(textRuns);
171
+
172
+ for (let group of groups) {
173
+ // Prepare content for translation, preserving spaces
174
+ const content = group.map(run => `|||${run.text}|||`).join('\n');
175
+
176
+ // Get translations
177
+ const translatedContent = await translate(content, lang);
178
+
179
+ // Parse translations
180
+ const translations = translatedContent
181
+ .split('|||')
182
+ .filter(t => t.trim())
183
+ .map(t => t.trim());
184
+
185
+ // Update document
186
+ group.forEach((run, index) => {
187
+ if (translations[index]) {
188
+ // Preserve original leading/trailing spaces
189
+ const originalText = run.text;
190
+ let translatedText = translations[index];
191
+
192
+ if (originalText.startsWith(' ')) {
193
+ translatedText = ' ' + translatedText;
194
+ }
195
+ if (originalText.endsWith(' ')) {
196
+ translatedText = translatedText + ' ';
197
+ }
198
+
199
+ setNestedValue(docJson, run.path, translatedText);
200
+ }
201
+ });
202
  }
203
  }
204
 
index.html CHANGED
@@ -37,7 +37,6 @@
37
 
38
  #translationControls {
39
  display: none;
40
- margin-bottom: 10px;
41
  }
42
 
43
  #languageSelect {
@@ -61,47 +60,6 @@
61
  </head>
62
  <body>
63
  <div id="app"></div>
64
- <script>
65
- // Add this before loading the main script
66
- window.servicesReady = false;
67
-
68
- // Create a function to check service status
69
- function checkServicesStatus() {
70
- fetch('/healthcheck')
71
- .then(response => {
72
- if (response.ok) {
73
- window.servicesReady = true;
74
- const translationControls = document.getElementById('translationControls');
75
- const statusIndicator = document.getElementById('statusIndicator');
76
- if (translationControls) {
77
- translationControls.style.display = 'block';
78
- }
79
- if (statusIndicator) {
80
- statusIndicator.style.display = 'none';
81
- }
82
- return true;
83
- }
84
- throw new Error('Services not ready');
85
- })
86
- .catch(error => {
87
- const statusIndicator = document.getElementById('statusIndicator');
88
- if (statusIndicator) {
89
- statusIndicator.innerHTML = '<span class="spinner"></span> Services starting...';
90
- }
91
- console.log('Waiting for services...', error);
92
- return false;
93
- });
94
- }
95
-
96
- // Check status every 2 seconds until ready
97
- const statusInterval = setInterval(() => {
98
- if (window.servicesReady) {
99
- clearInterval(statusInterval);
100
- } else {
101
- checkServicesStatus();
102
- }
103
- }, 2000);
104
- </script>
105
  <script src="/document-authoring.js"></script>
106
  </body>
107
  </html>
 
37
 
38
  #translationControls {
39
  display: none;
 
40
  }
41
 
42
  #languageSelect {
 
60
  </head>
61
  <body>
62
  <div id="app"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  <script src="/document-authoring.js"></script>
64
  </body>
65
  </html>