001: package jimm.properties;
002:
003: import java.util.*;
004: import java.io.*;
005: import java.awt.Dimension;
006: import java.awt.event.*;
007: import javax.swing.*;
008: import javax.swing.event.TreeSelectionEvent;
009: import javax.swing.event.TreeSelectionListener;
010: import javax.swing.tree.*;
011:
012: public class TranslateOMatic extends JFrame implements ActionListener,
013: TreeSelectionListener {
014:
015: // ================================================================
016:
017: /**
018: * A bundle association holds on to the "from" and "to" bundles, the
019: * prefix name (e.g., "datavision" or "menu"), and a list of
020: * exclusions---entries that should not be displayed.
021: */
022: class BundleAssoc {
023:
024: String prefix;
025: ResourceBundle from;
026: MutableResourceBundle to;
027: List exclusions;
028:
029: BundleAssoc(String prefix, ResourceBundle from,
030: MutableResourceBundle to) {
031: this .prefix = prefix;
032: this .from = from;
033: this .to = to;
034: exclusions = new ArrayList();
035: }
036:
037: void addExclusion(String str) {
038: exclusions.add(str);
039: }
040:
041: boolean exclude(String str) {
042: int pos = str.lastIndexOf('.');
043: if (pos >= 0)
044: str = str.substring(pos + 1);
045: return exclusions.contains(str);
046: }
047:
048: }
049:
050: // ================================================================
051:
052: /**
053: * Represents a single entry change; used to remember what was being
054: * edited.
055: */
056: class Translation {
057: MutableResourceBundle to;
058: String key;
059:
060: Translation(MutableResourceBundle to, String key) {
061: this .to = to;
062: this .key = key;
063: }
064:
065: void save(String str) {
066: to.setString(key, str.trim());
067: }
068: }
069:
070: // ================================================================
071:
072: /**
073: * A mutable resource bundle.
074: */
075: class MutableResourceBundle {
076:
077: String prefix;
078: String language;
079: String country;
080: ResourceBundle bundle;
081: HashMap newValues;
082:
083: MutableResourceBundle(String prefix, String language,
084: String country) {
085: this .prefix = prefix;
086: this .language = language;
087: this .country = country;
088: bundle = ResourceBundle.getBundle(prefix, new Locale(
089: language, country));
090: newValues = new HashMap();
091: }
092:
093: String getString(String key) {
094: String str = (String) newValues.get(key);
095: if (str == null)
096: str = bundle.getString(key);
097: return str;
098: }
099:
100: void setString(String key, String value) {
101: if (value != null) {
102: value = value.trim();
103: if (value.length() == 0) // Store null for empty strings
104: value = null;
105: }
106: newValues.put(key, value);
107: }
108:
109: String fileName() {
110: return prefix + "_" + language + "_" + country
111: + ".properties";
112: }
113: }
114:
115: // ================================================================
116:
117: static final Dimension WINDOW_SIZE = new Dimension(600, 350);
118: static final Dimension MIN_SIZE = new Dimension(100, 50);
119: static final int START_DIVIDER_LOCATION = 150;
120: static final int TEXT_FIELD_SIZE = 40;
121:
122: HashMap bundles;
123: ResourceBundle settings;
124: DefaultTreeModel model;
125: JTree tree;
126: JLabel fromField;
127: JTextField toField;
128: Translation xlation;
129: String encoding;
130:
131: public TranslateOMatic(String localeLanguage, String localeCountry,
132: String encodingName) {
133: super ("Translate-O-Matic");
134:
135: bundles = new HashMap();
136: settings = ResourceBundle.getBundle("translate");
137:
138: encoding = encodingName;
139: if (encoding == null)
140: encoding = settings.getString("default_encoding");
141: else
142: encoding = encoding.toUpperCase();
143:
144: buildModel(localeLanguage, localeCountry);
145: buildWindow();
146:
147: // Make sure we close the window when the user asks to close the window.
148: addWindowListener(new WindowAdapter() {
149: public void windowClosing(WindowEvent e) {
150: maybeClose();
151: }
152: });
153:
154: pack();
155: show();
156: }
157:
158: protected void buildModel(String localeLanguage,
159: String localeCountry) {
160: List prefixes = split(settings.getString("bundles"));
161: for (Iterator iter = prefixes.iterator(); iter.hasNext();) {
162: String prefix = (String) iter.next();
163: ResourceBundle from = ResourceBundle.getBundle(prefix);
164: MutableResourceBundle to = new MutableResourceBundle(
165: prefix, localeLanguage, localeCountry);
166:
167: BundleAssoc assoc = new BundleAssoc(prefix, from, to);
168: bundles.put(prefix, assoc);
169:
170: // Exclusions
171: try {
172: String exclusions = settings.getString("exclusions."
173: + prefix);
174: List l = split(exclusions);
175: for (Iterator iter2 = l.iterator(); iter2.hasNext();)
176: assoc.addExclusion(iter2.next().toString());
177: } catch (MissingResourceException mre) {
178: // Ignore missing resources
179: }
180: }
181:
182: DefaultMutableTreeNode top = new DefaultMutableTreeNode();
183: createNodes(top);
184: model = new DefaultTreeModel(top);
185: }
186:
187: /**
188: * Creates tree nodes.
189: *
190: * @param top top-level tree node
191: */
192: protected void createNodes(DefaultMutableTreeNode top) {
193: for (Iterator iter = bundles.values().iterator(); iter
194: .hasNext();)
195: createNode(top, (BundleAssoc) iter.next());
196: }
197:
198: protected void createNode(DefaultMutableTreeNode top,
199: BundleAssoc assoc) {
200: String name = assoc.prefix.substring(0, 1).toUpperCase()
201: + assoc.prefix.substring(1);
202: DefaultMutableTreeNode categoryNode = new DefaultMutableTreeNode(
203: name);
204: top.add(categoryNode);
205:
206: HashMap subnodes = new HashMap();
207: for (Enumeration e = assoc.from.getKeys(); e.hasMoreElements();) {
208: String key = e.nextElement().toString();
209: if (assoc.exclude(key))
210: continue;
211:
212: String firstPart = firstPartOf(key);
213: if (firstPart.equals(key))
214: categoryNode.add(new DefaultMutableTreeNode(key));
215: else {
216: DefaultMutableTreeNode subnode = (DefaultMutableTreeNode) subnodes
217: .get(firstPart);
218: if (subnode == null) {
219: subnode = new DefaultMutableTreeNode(firstPart);
220: subnodes.put(firstPart, subnode);
221: categoryNode.add(subnode);
222: }
223: subnode.add(new DefaultMutableTreeNode(key));
224: }
225: }
226: }
227:
228: /**
229: * Builds the window components.
230: */
231: protected void buildWindow() {
232: buildMenuBar();
233: buildContents();
234: getRootPane().setPreferredSize(WINDOW_SIZE);
235: }
236:
237: /**
238: * Builds the window menu bar.
239: */
240: protected void buildMenuBar() {
241: JMenuBar bar = new JMenuBar();
242: bar.add(buildFileMenu());
243: getRootPane().setJMenuBar(bar);
244: }
245:
246: /**
247: * Builds and returns the "File" menu.
248: *
249: * @return a menu
250: */
251: protected JMenu buildFileMenu() {
252: JMenu m = new JMenu("File");
253: JMenuItem i = new JMenuItem("Quit");
254: i.addActionListener(this );
255: m.add(i);
256: return m;
257: }
258:
259: /**
260: * Builds window contents.
261: */
262: protected void buildContents() {
263: buildTree();
264:
265: JScrollPane treeScrollPane = new JScrollPane(tree);
266: treeScrollPane.setMinimumSize(MIN_SIZE);
267:
268: Box box = Box.createVerticalBox();
269: box.add(fromField = new JLabel(" "));
270: fromField.setHorizontalAlignment(SwingConstants.LEFT);
271: box.add(toField = new JTextField(TEXT_FIELD_SIZE));
272: JPanel p = new JPanel();
273: p.add(box);
274: p.setMinimumSize(MIN_SIZE);
275:
276: JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
277: treeScrollPane, p);
278: sp.setDividerLocation(START_DIVIDER_LOCATION);
279: sp.setPreferredSize(WINDOW_SIZE);
280:
281: getContentPane().add(sp);
282: }
283:
284: protected void buildTree() {
285: tree = new JTree(model);
286: tree.setRootVisible(false);
287: tree.getSelectionModel().setSelectionMode(
288: TreeSelectionModel.SINGLE_TREE_SELECTION);
289:
290: // Listen for selection changes
291: tree.addTreeSelectionListener(this );
292: }
293:
294: public void valueChanged(TreeSelectionEvent e) {
295: // Save previously selected value
296: if (xlation != null)
297: xlation.save(toField.getText());
298:
299: TreePath path = e.getPath();
300: if (path == null)
301: return;
302:
303: // We ignore the 0'th entry which is an unnamed, unused root element.
304: Object[] elements = path.getPath();
305: if (elements.length < 3)
306: return;
307:
308: String bundleName = elements[1].toString().toLowerCase();
309: BundleAssoc assoc = (BundleAssoc) bundles.get(bundleName);
310:
311: String key = elements[elements.length - 1].toString();
312: String fromString = null, toString = null;
313: try {
314: fromString = assoc.from.getString(key);
315: toString = assoc.to.getString(key);
316:
317: xlation = new Translation(assoc.to, key);
318:
319: fromField.setText(fromString);
320: toField.setText(toString);
321: } catch (MissingResourceException mre) {
322: xlation = null;
323:
324: fromField.setText(" ");
325: toField.setText("");
326: }
327: }
328:
329: /**
330: * Handles user actions.
331: */
332: public void actionPerformed(ActionEvent e) {
333: String cmd = e.getActionCommand();
334: if ("Quit".equals(cmd))
335: maybeClose();
336: }
337:
338: /**
339: * Returns a string that can be written as a resource bundle value.
340: */
341: String escape(String str) {
342: if (str == null || str.length() == 0)
343: return str;
344:
345: StringBuffer buf = new StringBuffer();
346:
347: int len = str.length();
348: for (int i = 0; i < len; ++i) {
349: char c = str.charAt(i);
350: switch (c) {
351: case '=':
352: case ':':
353: buf.append('\\');
354: buf.append(c);
355: break;
356: case '\n':
357: buf.append("\\n");
358: break;
359: case '\r':
360: buf.append("\\r");
361: break;
362: case '\t':
363: buf.append("\\t");
364: break;
365: default:
366: if (i == 0 && Character.isWhitespace(c))
367: buf.append('\\');
368: buf.append(c);
369: break;
370: }
371: }
372:
373: return buf.toString();
374: }
375:
376: /**
377: * Save the file and close the window.
378: */
379: protected void save() {
380: try {
381: for (Iterator iter = bundles.values().iterator(); iter
382: .hasNext();) {
383: BundleAssoc assoc = (BundleAssoc) iter.next();
384: File f = new File(assoc.to.fileName());
385: PrintWriter out = new PrintWriter(
386: new OutputStreamWriter(new FileOutputStream(f),
387: encoding));
388:
389: for (Enumeration e = assoc.from.getKeys(); e
390: .hasMoreElements();) {
391: String key = e.nextElement().toString();
392: String val = escape(assoc.to.getString(key));
393: if (val != null)
394: out.println(key + " = " + val);
395: }
396:
397: out.flush();
398: out.close();
399: }
400: } catch (IOException ioe) {
401: System.err.println(ioe.toString());
402: }
403: }
404:
405: /**
406: * Save the file and close the window.
407: */
408: protected void maybeClose() {
409: // Save currently selected value
410: if (xlation != null)
411: xlation.save(toField.getText());
412:
413: save();
414: dispose();
415: System.exit(0);
416: }
417:
418: protected String firstPartOf(String key) {
419: int pos = key.indexOf('.');
420: return (pos == -1) ? key : key.substring(0, pos);
421: }
422:
423: protected List split(String str) {
424: if (str == null)
425: return null;
426:
427: ArrayList list = new ArrayList();
428:
429: int subStart, afterDelim = 0;
430: while ((subStart = str.indexOf(',', afterDelim)) != -1) {
431: list.add(str.substring(afterDelim, subStart).trim());
432: afterDelim = subStart + 1;
433: }
434: if (afterDelim <= str.length())
435: list.add(str.substring(afterDelim).trim());
436:
437: return list;
438: }
439:
440: public static void main(String[] args) {
441: try {
442: if (args[0].toLowerCase() == "en"
443: && args[1].toUpperCase() == "US") {
444: System.err.println("Can't modify en_US files");
445: System.exit(1);
446: }
447: new TranslateOMatic(args[0], args[1],
448: (args.length >= 3) ? args[2] : null);
449: } catch (Exception e) {
450: e.printStackTrace();
451: }
452: }
453:
454: }
|