i18nema 0.0.4 → 0.0.5

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.
data/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  Fast I18n backend to keep things running smoothly.
4
4
 
5
5
  I18nema is a drop-in replacement for I18n::Backend::Simple, for faster
6
- lookups and quicker gc runs. Translations are stored outside of the ruby
7
- heap, and lookups happen in C (rather than the usual inject on nested
8
- ruby hashes).
6
+ lookups (2x!) and quicker GC runs (ymmv). Translations are stored
7
+ outside of the ruby heap, and lookups happen in C (rather than the usual
8
+ inject on nested ruby hashes).
9
9
 
10
10
  ## How do I use it?
11
11
 
@@ -15,11 +15,113 @@ and then do something like this in an initializer:
15
15
 
16
16
  I18n.backend = I18nema::Backend.new
17
17
 
18
- As with I18n::Backend::Simple, you can pull in additional features, e.g.
18
+ You can pull in additional features, e.g.
19
19
 
20
20
  I18nema::Backend.send(:include, I18n::Backend::Fallbacks)
21
21
 
22
- ## Notes
22
+ As with regular I18n, you should probably load translations before you
23
+ fork, so that all processes can use the same translations in memory. In
24
+ an initializer, just do `I18n.backend.init_translations`.
25
+
26
+ ## What sort of improvements will I see?
27
+
28
+ ### Faster Startup
29
+
30
+ Loading all the translations into memory is dramtically faster with
31
+ I18nema (about 4x). While this is just a one time hit, it's pretty
32
+ noticeable when you're waiting on it (e.g. console, specs). In
33
+ [canvas-lms](https://github/com/instructure/canvas-lms), I18nema brings
34
+ it down to just over half a second (from almost 2.5).
35
+
36
+ ### Faster GC Runs
37
+
38
+ Because there are fewer ruby objects, the periodic GC runs will be
39
+ proportionally faster. How much faster is a question of how many
40
+ translations you have versus how many other ruby objects. Applications
41
+ that are localized in more languages should see a bigger boost (since
42
+ the translations make up a bigger share of the original ObjectSpace).
43
+
44
+ For example, [canvas-lms](https://github/com/instructure/canvas-lms) is
45
+ translated into seven other languages, and I18nema reduces (startup)
46
+ ObjectSpace by about 18% and GC runtime by about 11%.
47
+
48
+ I18nema also moves I18n's normalized_key_cache into C structs. This key
49
+ cache grows over time (it eventually holds a key/value for every
50
+ translation key used in the app), so that's another area where I18nema
51
+ is nicer on ObjectSpace than vanilla I18n.
52
+
53
+ ### Faster Translation Lookups
54
+
55
+ Simple lookups (i.e. no options or interpolation) take about 55% less
56
+ time, and that holds true no matter how deeply scoped your keys are
57
+ (e.g. `foo.bar.baz.lol`).
58
+
59
+ Lookups with options are also faster (about 35% less time). The gains
60
+ aren't quite as big there yet, since proportionally more of the time is
61
+ spent in unoptimized places like `I18n#interpolate`.
62
+
63
+ ## Show me the benchmarks
64
+
65
+ Here are some basic ones done with `Benchmark.bmbm` (edited for brevity)
66
+ We run `I18n.translate` 100000 times on 4 different translation keys.
67
+ The `n` in `translate(n)` denotes how many parts there are in the key,
68
+ e.g. `I18n.t('foo') -> 1`, `I18n.t('foo.bar') -> 2`
69
+
70
+ ### simple `translate` (no options)
71
+
72
+ #### I18nema
73
+
74
+ user system total real
75
+ translate(1): 0.960000 0.010000 0.970000 ( 0.963384)
76
+ translate(2): 1.070000 0.010000 1.080000 ( 1.068789)
77
+ translate(3): 1.080000 0.000000 1.080000 ( 1.083826)
78
+ translate(4): 1.260000 0.010000 1.270000 ( 1.263967)
79
+
80
+ #### I18n
81
+
82
+ user system total real
83
+ translate(1): 2.140000 0.000000 2.140000 ( 2.137579)
84
+ translate(2): 2.380000 0.000000 2.380000 ( 2.382720)
85
+ translate(3): 2.420000 0.000000 2.420000 ( 2.428245)
86
+ translate(4): 2.510000 0.000000 2.510000 ( 2.514133)
87
+
88
+ ### `translate` with options (locale, interpolation)
89
+
90
+ #### I18nema
91
+
92
+ user system total real
93
+ translate(1): 1.770000 0.000000 1.770000 ( 1.771934)
94
+ translate(2): 1.870000 0.010000 1.880000 ( 1.880586)
95
+ translate(3): 1.890000 0.000000 1.890000 ( 1.893065)
96
+ translate(4): 2.080000 0.000000 2.080000 ( 2.083093)
97
+
98
+ #### I18n
99
+
100
+ user system total real
101
+ translate(1): 2.720000 0.000000 2.720000 ( 2.720541)
102
+ translate(2): 2.950000 0.010000 2.960000 ( 2.953175)
103
+ translate(3): 3.010000 0.010000 3.020000 ( 3.028288)
104
+ translate(4): 3.120000 0.010000 3.130000 ( 3.117135)
105
+
106
+
107
+ ## OK, so what's the catch?
108
+
109
+ I18nema is still a work in progress, so there are some compatibility
110
+ notes you should be aware of:
111
+
112
+ I18nema requires ruby 1.9.3 or later.
113
+
114
+ I18nema only supports `.yml` translation files (no `.rb`).
115
+
116
+ I18nema requires UTF-8 `.yml` files. That means that your translations
117
+ should actually be in their UTF-8 form (e.g. "Contraseña"), not some
118
+ escaped representation. I18nema uses a simplified syck implementation
119
+ and does not support many optional yml types (e.g. `binary`).
120
+
121
+ I18nema doesn't yet support symbols as translation *values* (note that
122
+ symbol [keys](http://guides.rubyonrails.org/i18n.html#basic-lookup-scopes-and-nested-keys)
123
+ and [defaults](http://guides.rubyonrails.org/i18n.html#defaults) work
124
+ just fine). Symbol values in your `.yml` file can be used in the same
125
+ way that symbol defaults can, i.e. they tell I18n to find the
126
+ translation under some other key.
23
127
 
24
- You should probably make sure translations are loaded before you fork. In
25
- an initializer, just do `I18n.backend.init_translations`
@@ -1,7 +1,7 @@
1
1
  #include <ruby.h>
2
2
  #include <ruby/encoding.h>
3
- #include <syck.h>
4
- #include "uthash.h"
3
+ #include "vendor/syck.h"
4
+ #include "vendor/uthash.h"
5
5
 
6
6
  VALUE I18nema = Qnil,
7
7
  I18nemaBackend = Qnil,
@@ -12,8 +12,10 @@ struct i_key_value;
12
12
  static VALUE array_to_rarray(struct i_object *array);
13
13
  static VALUE hash_to_rhash(struct i_object *hash);
14
14
  static void merge_hash(struct i_object *hash, struct i_object *other_hash);
15
- static void delete_hash(struct i_key_value **hash, int delete_child_objects);
16
- static void delete_object(struct i_object *object, int delete_child_objects);
15
+ static void delete_hash(struct i_key_value **hash, int recurse);
16
+ static void delete_object(struct i_object *object, int recurse);
17
+ static void delete_object_r(struct i_object *object);
18
+ static VALUE normalize_key(VALUE self, VALUE key, VALUE separator);
17
19
 
18
20
  enum i_object_type {
19
21
  i_type_none,
@@ -105,13 +107,37 @@ hash_to_rhash(i_object_t *hash)
105
107
  }
106
108
 
107
109
  static i_object_t*
108
- root_object_get(VALUE self)
110
+ i_object_get(VALUE self, const char *iv)
109
111
  {
110
- i_object_t *root_object;
111
- VALUE translations;
112
- translations = rb_iv_get(self, "@translations");
113
- Data_Get_Struct(translations, i_object_t, root_object);
114
- return root_object;
112
+ i_object_t *object;
113
+ VALUE wrapped;
114
+ wrapped = rb_iv_get(self, iv);
115
+ Data_Get_Struct(wrapped, i_object_t, object);
116
+ return object;
117
+ }
118
+
119
+ static i_object_t*
120
+ translations_get(VALUE self)
121
+ {
122
+ return i_object_get(self, "@translations");
123
+ }
124
+
125
+ static i_object_t*
126
+ normalized_key_cache_get(VALUE self)
127
+ {
128
+ return i_object_get(self, "@normalized_key_cache");
129
+ }
130
+
131
+ static i_object_t*
132
+ hash_get(i_object_t *current, VALUE *keys, int num_keys)
133
+ {
134
+ i_key_value_t *kv = NULL;
135
+ for (int i = 0; i < num_keys && current != NULL && current->type == i_type_hash; i++) {
136
+ Check_Type(keys[i], T_STRING);
137
+ HASH_FIND_STR(current->data.hash, StringValueCStr(keys[i]), kv);
138
+ current = kv == NULL ? NULL : kv->value;
139
+ }
140
+ return current;
115
141
  }
116
142
 
117
143
  /*
@@ -127,37 +153,25 @@ root_object_get(VALUE self)
127
153
  static VALUE
128
154
  direct_lookup(int argc, VALUE *argv, VALUE self)
129
155
  {
130
- i_object_t *result = root_object_get(self);;
131
- i_key_value_t *kv = NULL;
132
- VALUE rs;
133
- char *s;
134
-
135
- for (int i = 0; i < argc && result != NULL && result->type == i_type_hash; i++) {
136
- rs = rb_funcall(argv[i], s_to_s, 0);
137
- s = StringValueCStr(rs);
138
- HASH_FIND_STR(result->data.hash, s, kv);
139
- result = kv == NULL ? NULL : kv->value;
140
- }
141
-
142
- return i_object_to_robject(result);
156
+ i_object_t *translations = translations_get(self);
157
+ return i_object_to_robject(hash_get(translations, argv, argc));
143
158
  }
144
159
 
145
160
  static void
146
- empty_object(i_object_t *object, int delete_child_objects)
161
+ empty_object(i_object_t *object, int recurse)
147
162
  {
148
163
  if (object == NULL)
149
164
  return;
150
165
 
151
166
  switch (object->type) {
152
167
  case i_type_array:
153
- if (delete_child_objects) {
168
+ if (recurse)
154
169
  for (unsigned long i = 0; i < object->size; i++)
155
- delete_object(object->data.array[i], 1);
156
- }
170
+ delete_object_r(object->data.array[i]);
157
171
  xfree(object->data.array);
158
172
  break;
159
173
  case i_type_hash:
160
- delete_hash(&object->data.hash, delete_child_objects);
174
+ delete_hash(&object->data.hash, recurse);
161
175
  break;
162
176
  case i_type_none:
163
177
  break;
@@ -168,35 +182,35 @@ empty_object(i_object_t *object, int delete_child_objects)
168
182
  }
169
183
 
170
184
  static void
171
- delete_object_r(i_object_t *object)
185
+ delete_object(i_object_t *object, int recurse)
172
186
  {
173
- delete_object(object, 1);
187
+ empty_object(object, recurse);
188
+ if (object->type != i_type_null && object->type != i_type_true && object->type != i_type_false)
189
+ xfree(object);
174
190
  }
175
191
 
176
192
  static void
177
- delete_object(i_object_t *object, int delete_child_objects)
193
+ delete_object_r(i_object_t *object)
178
194
  {
179
- empty_object(object, delete_child_objects);
180
- if (object->type != i_type_null && object->type != i_type_true && object->type != i_type_false)
181
- xfree(object);
195
+ delete_object(object, 1);
182
196
  }
183
197
 
184
198
  static void
185
199
  delete_key_value(i_key_value_t *kv, int delete_value)
186
200
  {
187
201
  if (delete_value)
188
- delete_object(kv->value, 1);
202
+ delete_object_r(kv->value);
189
203
  xfree(kv->key);
190
204
  xfree(kv);
191
205
  }
192
206
 
193
207
  static void
194
- delete_hash(i_key_value_t **hash, int delete_child_objects)
208
+ delete_hash(i_key_value_t **hash, int recurse)
195
209
  {
196
210
  i_key_value_t *kv, *tmp;
197
211
  HASH_ITER(hh, *hash, kv, tmp) {
198
212
  HASH_DEL(*hash, kv);
199
- delete_key_value(kv, delete_child_objects);
213
+ delete_key_value(kv, recurse);
200
214
  }
201
215
  }
202
216
 
@@ -227,7 +241,7 @@ merge_hash(i_object_t *hash, i_object_t *other_hash)
227
241
  HASH_DEL(other_hash->data.hash, kv);
228
242
  add_key_value(&hash->data.hash, kv);
229
243
  }
230
- delete_object(other_hash, 1);
244
+ delete_object_r(other_hash);
231
245
  }
232
246
 
233
247
  static int
@@ -236,7 +250,7 @@ delete_syck_st_entry(char *key, char *value, char *arg)
236
250
  i_object_t *object = (i_object_t *)value;
237
251
  // key object whose string we have yoinked into a kv
238
252
  if (object->type == i_type_none)
239
- delete_object(object, 1);
253
+ delete_object_r(object);
240
254
  return ST_DELETE;
241
255
  }
242
256
 
@@ -270,18 +284,53 @@ handle_syck_badanchor(SyckParser *parser, char *anchor)
270
284
  return NULL;
271
285
  }
272
286
 
287
+ static char*
288
+ new_string(char *orig, long len)
289
+ {
290
+ char *str = xmalloc(len + 1);
291
+ strncpy(str, orig, len);
292
+ str[len] = '\0';
293
+ return str;
294
+ }
295
+
273
296
  static i_object_t*
274
297
  new_string_object(char *str, long len)
275
298
  {
276
299
  i_object_t *object = ALLOC(i_object_t);
277
300
  object->type = i_type_string;
278
301
  object->size = len;
279
- object->data.string = xmalloc(len + 1);
280
- strncpy(object->data.string, str, len);
281
- object->data.string[len] = '\0';
302
+ object->data.string = new_string(str, len);
303
+ return object;
304
+ }
305
+
306
+ static i_object_t*
307
+ new_array_object(long size)
308
+ {
309
+ i_object_t *object = ALLOC(i_object_t);
310
+ object->type = i_type_array;
311
+ object->size = size;
312
+ object->data.array = ALLOC_N(i_object_t*, size);
282
313
  return object;
283
314
  }
284
315
 
316
+ static i_object_t*
317
+ new_hash_object()
318
+ {
319
+ i_object_t *object = ALLOC(i_object_t);
320
+ object->type = i_type_hash;
321
+ object->data.hash = NULL;
322
+ return object;
323
+ }
324
+
325
+ static i_key_value_t*
326
+ new_key_value(char *key, i_object_t *value)
327
+ {
328
+ i_key_value_t *kv = ALLOC(i_key_value_t);
329
+ kv->key = key;
330
+ kv->value = value;
331
+ return kv;
332
+ }
333
+
285
334
  static SYMID
286
335
  handle_syck_node(SyckParser *parser, SyckNode *node)
287
336
  {
@@ -315,10 +364,7 @@ handle_syck_node(SyckParser *parser, SyckNode *node)
315
364
  }
316
365
  break;
317
366
  case syck_seq_kind:
318
- result = ALLOC(i_object_t);
319
- result->type = i_type_array;
320
- result->size = node->data.list->idx;
321
- result->data.array = ALLOC_N(i_object_t*, node->data.list->idx);
367
+ result = new_array_object(node->data.list->idx);
322
368
  for (long i = 0; i < node->data.list->idx; i++) {
323
369
  i_object_t *item = NULL;
324
370
 
@@ -330,9 +376,7 @@ handle_syck_node(SyckParser *parser, SyckNode *node)
330
376
  }
331
377
  break;
332
378
  case syck_map_kind:
333
- result = ALLOC(i_object_t);
334
- result->type = i_type_hash;
335
- result->data.hash = NULL;
379
+ result = new_hash_object();
336
380
  for (long i = 0; i < node->data.pairs->idx; i++) {
337
381
  i_object_t *key = NULL, *value = NULL;
338
382
 
@@ -341,11 +385,8 @@ handle_syck_node(SyckParser *parser, SyckNode *node)
341
385
  oid = syck_map_read(node, map_value, i);
342
386
  syck_lookup_sym(parser, oid, (void **)&value);
343
387
 
344
- i_key_value_t *kv;
345
- kv = ALLOC(i_key_value_t);
346
- kv->key = key->data.string;
388
+ i_key_value_t *kv = new_key_value(key->data.string, value);
347
389
  key->type = i_type_none; // so we know to free this node in delete_syck_st_entry
348
- kv->value = value;
349
390
  if (value->type == i_type_string)
350
391
  current_translation_count++;
351
392
  add_key_value(&result->data.hash, kv);
@@ -371,7 +412,7 @@ static VALUE
371
412
  load_yml_string(VALUE self, VALUE yml)
372
413
  {
373
414
  SYMID oid;
374
- i_object_t *root_object = root_object_get(self);
415
+ i_object_t *root_object = translations_get(self);
375
416
  i_object_t *new_root_object = NULL;
376
417
  current_translation_count = 0;
377
418
  SyckParser* parser = syck_new_parser();
@@ -387,7 +428,7 @@ load_yml_string(VALUE self, VALUE yml)
387
428
  st_foreach(parser->syms, delete_syck_st_entry, 0);
388
429
  syck_free_parser(parser);
389
430
  if (new_root_object == NULL || new_root_object->type != i_type_hash) {
390
- delete_object(new_root_object, 1);
431
+ delete_object_r(new_root_object);
391
432
  rb_raise(I18nemaBackendLoadError, "root yml node is not a hash");
392
433
  }
393
434
  merge_hash(root_object, new_root_object);
@@ -409,7 +450,7 @@ available_locales(VALUE self)
409
450
  {
410
451
  if (!RTEST(rb_iv_get(self, "@initialized")))
411
452
  rb_funcall(self, s_init_translations, 0);
412
- i_object_t *root_object = root_object_get(self);
453
+ i_object_t *root_object = translations_get(self);
413
454
  i_key_value_t *current = root_object->data.hash;
414
455
  VALUE ary = rb_ary_new2(0);
415
456
 
@@ -431,22 +472,96 @@ available_locales(VALUE self)
431
472
  static VALUE
432
473
  reload(VALUE self)
433
474
  {
434
- i_object_t *root_object = root_object_get(self);
475
+ i_object_t *root_object = translations_get(self);
435
476
  empty_object(root_object, 1);
436
477
  rb_iv_set(self, "@initialized", Qfalse);
437
- root_object = NULL;
438
478
  return Qtrue;
439
479
  }
440
480
 
481
+ static VALUE
482
+ join_array_key(VALUE self, VALUE key, VALUE separator)
483
+ {
484
+ long len = RARRAY_LEN(key);
485
+ if (len == 0)
486
+ return rb_str_new("", 0);
487
+
488
+ VALUE ret = rb_ary_join(normalize_key(self, RARRAY_PTR(key)[0], separator), separator);
489
+ for (long i = 1; i < len; i++) {
490
+ rb_str_concat(ret, separator);
491
+ rb_str_concat(ret, rb_ary_join(normalize_key(self, RARRAY_PTR(key)[i], separator), separator));
492
+ }
493
+ return ret;
494
+ }
495
+
496
+ /*
497
+ * call-seq:
498
+ * backend.normalize_key(key, separator) -> key
499
+ *
500
+ * Normalizes and splits a key based on the separator.
501
+ *
502
+ * backend.normalize_key "asdf", "." #=> ["asdf"]
503
+ * backend.normalize_key "a.b.c", "." #=> ["a", "b", "c"]
504
+ * backend.normalize_key "a.b.c", ":" #=> ["a.b.c"]
505
+ * backend.normalize_key %{a b.c}, "." #=> ["a", "b", "c"]
506
+ */
507
+
508
+ static VALUE
509
+ normalize_key(VALUE self, VALUE key, VALUE separator)
510
+ {
511
+ Check_Type(separator, T_STRING);
512
+
513
+ i_object_t *key_map = normalized_key_cache_get(self),
514
+ *sub_map = hash_get(key_map, &separator, 1);
515
+ if (sub_map == NULL) {
516
+ sub_map = new_hash_object();
517
+ char *key = new_string(RSTRING_PTR(separator), RSTRING_LEN(separator));
518
+ i_key_value_t *kv = new_key_value(key, sub_map);
519
+ add_key_value(&key_map->data.hash, kv);
520
+ }
521
+
522
+ if (TYPE(key) == T_ARRAY)
523
+ key = join_array_key(self, key, separator);
524
+ else if (TYPE(key) != T_STRING)
525
+ key = rb_funcall(key, s_to_s, 0);
526
+
527
+ i_object_t *key_frd = hash_get(sub_map, &key, 1);
528
+
529
+ if (key_frd == NULL) {
530
+ char *sep = StringValueCStr(separator);
531
+ VALUE parts = rb_str_split(key, sep);
532
+ long parts_len = RARRAY_LEN(parts),
533
+ skipped = 0;
534
+ key_frd = new_array_object(parts_len);
535
+ for (long i = 0; i < parts_len; i++) {
536
+ VALUE part = RARRAY_PTR(parts)[i];
537
+ // TODO: don't alloc for empty strings, since we discard them
538
+ if (RSTRING_LEN(part) == 0)
539
+ skipped++;
540
+ else
541
+ key_frd->data.array[i - skipped] = new_string_object(RSTRING_PTR(part), RSTRING_LEN(part));
542
+ }
543
+ key_frd->size -= skipped;
544
+
545
+ char *key_orig = new_string(RSTRING_PTR(key), RSTRING_LEN(key));
546
+ i_key_value_t *kv = new_key_value(key_orig, key_frd);
547
+ add_key_value(&sub_map->data.hash, kv);
548
+ }
549
+ return i_object_to_robject(key_frd);
550
+ }
551
+
441
552
  static VALUE
442
553
  initialize(VALUE self)
443
554
  {
444
- VALUE translations;
445
- i_object_t *root_object = ALLOC(i_object_t);
446
- root_object->type = i_type_hash;
447
- root_object->data.hash = NULL;
555
+ VALUE translations, key_cache;
556
+
557
+ i_object_t *root_object = new_hash_object();
448
558
  translations = Data_Wrap_Struct(I18nemaBackend, 0, delete_object_r, root_object);
449
559
  rb_iv_set(self, "@translations", translations);
560
+
561
+ i_object_t *key_map = new_hash_object();
562
+ key_cache = Data_Wrap_Struct(I18nemaBackend, 0, delete_object_r, key_map);
563
+ rb_iv_set(self, "@normalized_key_cache", key_cache);
564
+
450
565
  return self;
451
566
  }
452
567
 
@@ -471,4 +586,5 @@ Init_i18nema()
471
586
  rb_define_method(I18nemaBackend, "available_locales", available_locales, 0);
472
587
  rb_define_method(I18nemaBackend, "reload!", reload, 0);
473
588
  rb_define_method(I18nemaBackend, "direct_lookup", direct_lookup, -1);
589
+ rb_define_method(I18nemaBackend, "normalize_key", normalize_key, 2);
474
590
  }
File without changes
@@ -18,6 +18,36 @@ module I18nema
18
18
  @initialized = true
19
19
  end
20
20
 
21
+ RESERVED_KEY_MAP = Hash[I18n::RESERVED_KEYS.map{|k|[k,true]}]
22
+
23
+ def translate(locale, key, options = {})
24
+ raise I18n::InvalidLocale.new(locale) unless locale
25
+ entry = key && lookup(locale, key, options[:scope], options)
26
+
27
+ if options.empty?
28
+ entry = resolve(locale, key, entry, options)
29
+ else
30
+ count, default = options.values_at(:count, :default)
31
+ # significant speedup over Hash#except
32
+ values = options.reject { |key, value| RESERVED_KEY_MAP.key?(key) }
33
+ entry = entry.nil? && default ?
34
+ default(locale, key, default, options) : resolve(locale, key, entry, options)
35
+ end
36
+
37
+ throw(:exception, I18n::MissingTranslation.new(locale, key, options)) if entry.nil?
38
+ # no need to dup, since I18nema gives us a new string
39
+
40
+ entry = pluralize(locale, entry, count) if count
41
+ entry = interpolate(locale, entry, values) if values
42
+ entry
43
+ end
44
+
45
+ alias_method :reload_old, :reload!
46
+ def reload!
47
+ debugger
48
+ reload_old
49
+ end
50
+
21
51
  protected
22
52
  def load_file(filename)
23
53
  type = File.extname(filename).tr('.', '').downcase
@@ -35,8 +65,17 @@ module I18nema
35
65
 
36
66
  def lookup(locale, key, scope = [], options = {})
37
67
  init_translations unless initialized?
38
- keys = I18n.normalize_keys(locale, key, scope, options[:separator])
68
+ keys = normalize_keys(locale, key, scope, options[:separator])
39
69
  direct_lookup(*keys)
40
70
  end
71
+
72
+ def normalize_keys(locale, key, scope, separator = nil)
73
+ separator ||= I18n.default_separator
74
+
75
+ keys = [locale.to_s]
76
+ keys.concat normalize_key(scope, separator) if scope && scope.size > 0
77
+ keys.concat normalize_key(key, separator)
78
+ keys
79
+ end
41
80
  end
42
81
  end
@@ -68,10 +68,6 @@ class I18nemaTest < Test::Unit::TestCase
68
68
  assert_equal("root yml node is not a hash", exception.message)
69
69
  assert_equal({}, backend.direct_lookup)
70
70
 
71
- # FIXME ... ruby syck does this differently, and we get a non
72
- # i_object_t as the root node, causing delete_object to asplode when
73
- # it tries to free a garbage pointer
74
- #
75
71
  exception = assert_raise(I18nema::Backend::LoadError) {
76
72
  backend.load_yml_string("en:\n foo: \"lol\"\n\tbar: notabs!")
77
73
  }
@@ -84,4 +80,18 @@ class I18nemaTest < Test::Unit::TestCase
84
80
  assert_match(/bad anchor `a'/, exception.message)
85
81
  assert_equal({}, backend.direct_lookup)
86
82
  end
83
+
84
+ def test_normalize_key
85
+ backend = I18nema::Backend.new
86
+ assert_equal %w{asdf},
87
+ backend.normalize_key("asdf", ".")
88
+ assert_equal %w{asdf asdf},
89
+ backend.normalize_key("asdf.asdf", ".")
90
+ assert_equal %w{asdf.asdf},
91
+ backend.normalize_key("asdf.asdf", ",")
92
+ assert_equal %w{asdf asdf},
93
+ backend.normalize_key("asdf...asdf", ".")
94
+ assert_equal %w{asdf asdf asdf},
95
+ backend.normalize_key(%w{asdf asdf.asdf}, ".")
96
+ end
87
97
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18nema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-30 00:00:00.000000000 Z
12
+ date: 2013-06-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: i18n
@@ -55,11 +55,8 @@ files:
55
55
  - Rakefile
56
56
  - README.md
57
57
  - ext/i18nema/i18nema.c
58
- - ext/i18nema/extconf.h
59
- - ext/i18nema/gram.h
60
- - ext/i18nema/syck.h
61
- - ext/i18nema/uthash.h
62
- - ext/i18nema/yamlbyte.h
58
+ - ext/i18nema/vendor/syck.h
59
+ - ext/i18nema/vendor/uthash.h
63
60
  - ext/i18nema/extconf.rb
64
61
  - ext/i18nema/mkrf_conf.rb
65
62
  - lib/i18nema/core_ext/hash.rb
@@ -1,3 +0,0 @@
1
- #ifndef EXTCONF_H
2
- #define EXTCONF_H
3
- #endif
@@ -1,79 +0,0 @@
1
- /* A Bison parser, made by GNU Bison 1.875d. */
2
-
3
- /* Skeleton parser for Yacc-like parsing with Bison,
4
- Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
5
-
6
- This program is free software; you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation; either version 2, or (at your option)
9
- any later version.
10
-
11
- This program is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with this program; if not, write to the Free Software
18
- Foundation, Inc., 59 Temple Place - Suite 330,
19
- Boston, MA 02111-1307, USA. */
20
-
21
- /* As a special exception, when this file is copied by Bison into a
22
- Bison output file, you may use that output file without restriction.
23
- This special exception was added by the Free Software Foundation
24
- in version 1.24 of Bison. */
25
-
26
- /* Tokens. */
27
- #ifndef YYTOKENTYPE
28
- # define YYTOKENTYPE
29
- /* Put the tokens into the symbol table, so that GDB and other debuggers
30
- know about them. */
31
- enum yytokentype {
32
- YAML_ANCHOR = 258,
33
- YAML_ALIAS = 259,
34
- YAML_TRANSFER = 260,
35
- YAML_TAGURI = 261,
36
- YAML_ITRANSFER = 262,
37
- YAML_WORD = 263,
38
- YAML_PLAIN = 264,
39
- YAML_BLOCK = 265,
40
- YAML_DOCSEP = 266,
41
- YAML_IOPEN = 267,
42
- YAML_INDENT = 268,
43
- YAML_IEND = 269
44
- };
45
- #endif
46
- #define YAML_ANCHOR 258
47
- #define YAML_ALIAS 259
48
- #define YAML_TRANSFER 260
49
- #define YAML_TAGURI 261
50
- #define YAML_ITRANSFER 262
51
- #define YAML_WORD 263
52
- #define YAML_PLAIN 264
53
- #define YAML_BLOCK 265
54
- #define YAML_DOCSEP 266
55
- #define YAML_IOPEN 267
56
- #define YAML_INDENT 268
57
- #define YAML_IEND 269
58
-
59
-
60
-
61
-
62
- #if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
63
- #line 35 "gram.y"
64
- typedef union YYSTYPE {
65
- SYMID nodeId;
66
- SyckNode *nodeData;
67
- char *name;
68
- } YYSTYPE;
69
- /* Line 1285 of yacc.c. */
70
- #line 71 "gram.h"
71
- # define yystype YYSTYPE /* obsolescent; will be withdrawn */
72
- # define YYSTYPE_IS_DECLARED 1
73
- # define YYSTYPE_IS_TRIVIAL 1
74
- #endif
75
-
76
-
77
-
78
-
79
-
@@ -1,171 +0,0 @@
1
- /* yamlbyte.h
2
- *
3
- * The YAML bytecode "C" interface header file. See the YAML bytecode
4
- * reference for bytecode sequence rules and for the meaning of each
5
- * bytecode.
6
- */
7
-
8
- #ifndef YAMLBYTE_H
9
- #define YAMLBYTE_H
10
- #include <stddef.h>
11
-
12
- /* define what a character is */
13
- typedef unsigned char yamlbyte_utf8_t;
14
- typedef unsigned short yamlbyte_utf16_t;
15
- #ifdef YAMLBYTE_UTF8
16
- #ifdef YAMLBYTE_UTF16
17
- #error Must only define YAMLBYTE_UTF8 or YAMLBYTE_UTF16
18
- #endif
19
- typedef yamlbyte_utf8_t yamlbyte_char_t;
20
- #else
21
- #ifdef YAMLBYTE_UTF16
22
- typedef yamlbyte_utf16_t yamlbyte_char_t;
23
- #else
24
- #error Must define YAMLBYTE_UTF8 or YAMLBYTE_UTF16
25
- #endif
26
- #endif
27
-
28
- /* specify list of bytecodes */
29
- #define YAMLBYTE_FINISH ((yamlbyte_char_t) 0)
30
- #define YAMLBYTE_DOCUMENT ((yamlbyte_char_t)'D')
31
- #define YAMLBYTE_DIRECTIVE ((yamlbyte_char_t)'V')
32
- #define YAMLBYTE_PAUSE ((yamlbyte_char_t)'P')
33
- #define YAMLBYTE_MAPPING ((yamlbyte_char_t)'M')
34
- #define YAMLBYTE_SEQUENCE ((yamlbyte_char_t)'Q')
35
- #define YAMLBYTE_END_BRANCH ((yamlbyte_char_t)'E')
36
- #define YAMLBYTE_SCALAR ((yamlbyte_char_t)'S')
37
- #define YAMLBYTE_CONTINUE ((yamlbyte_char_t)'C')
38
- #define YAMLBYTE_NEWLINE ((yamlbyte_char_t)'N')
39
- #define YAMLBYTE_NULLCHAR ((yamlbyte_char_t)'Z')
40
- #define YAMLBYTE_ANCHOR ((yamlbyte_char_t)'A')
41
- #define YAMLBYTE_ALIAS ((yamlbyte_char_t)'R')
42
- #define YAMLBYTE_TRANSFER ((yamlbyte_char_t)'T')
43
- /* formatting bytecodes */
44
- #define YAMLBYTE_COMMENT ((yamlbyte_char_t)'c')
45
- #define YAMLBYTE_INDENT ((yamlbyte_char_t)'i')
46
- #define YAMLBYTE_STYLE ((yamlbyte_char_t)'s')
47
- /* other bytecodes */
48
- #define YAMLBYTE_LINE_NUMBER ((yamlbyte_char_t)'#')
49
- #define YAMLBYTE_WHOLE_SCALAR ((yamlbyte_char_t)'<')
50
- #define YAMLBYTE_NOTICE ((yamlbyte_char_t)'!')
51
- #define YAMLBYTE_SPAN ((yamlbyte_char_t)')')
52
- #define YAMLBYTE_ALLOC ((yamlbyte_char_t)'@')
53
-
54
- /* second level style bytecodes, ie "s>" */
55
- #define YAMLBYTE_FLOW ((yamlbyte_char_t)'>')
56
- #define YAMLBYTE_LITERAL ((yamlbyte_char_t)'|')
57
- #define YAMLBYTE_BLOCK ((yamlbyte_char_t)'b')
58
- #define YAMLBYTE_PLAIN ((yamlbyte_char_t)'p')
59
- #define YAMLBYTE_INLINE_MAPPING ((yamlbyte_char_t)'{')
60
- #define YAMLBYTE_INLINE_SEQUENCE ((yamlbyte_char_t)'[')
61
- #define YAMLBYTE_SINGLE_QUOTED ((yamlbyte_char_t)39)
62
- #define YAMLBYTE_DOUBLE_QUOTED ((yamlbyte_char_t)'"')
63
-
64
- /*
65
- * The "C" API has two variants, one based on instructions,
66
- * with events delivered via pointers; and the other one
67
- * is character based where one or more instructions are
68
- * serialized into a buffer.
69
- *
70
- * Note: In the instruction based API, WHOLE_SCALAR does
71
- * not have the '<here' marshalling stuff.
72
- */
73
-
74
- typedef void * yamlbyte_consumer_t;
75
- typedef void * yamlbyte_producer_t;
76
-
77
- /* push and pull APIs need a way to communicate results */
78
- typedef enum {
79
- YAMLBYTE_OK = 0, /* proceed */
80
- YAMLBYTE_E_MEMORY = 'M', /* could not allocate memory */
81
- YAMLBYTE_E_READ = 'R', /* input stream read error */
82
- YAMLBYTE_E_WRITE = 'W', /* output stream write error */
83
- YAMLBYTE_E_OTHER = '?', /* some other error condition */
84
- YAMLBYTE_E_PARSE = 'P', /* parse error, check bytecodes */
85
- YAMLBYTE_MAX
86
- } yamlbyte_result_t;
87
-
88
- typedef const yamlbyte_char_t *yamlbyte_buff_t;
89
-
90
- /*
91
- * The "Instruction" API
92
- */
93
-
94
- typedef struct yaml_instruction {
95
- yamlbyte_char_t bytecode;
96
- yamlbyte_buff_t start;
97
- yamlbyte_buff_t finish; /* open range, *finish is _not_ part */
98
- } *yamlbyte_inst_t;
99
-
100
- /* producer pushes the instruction with one bytecode event to the
101
- * consumer; if the consumer's result is not YAMLBYTE_OK, then
102
- * the producer should stop */
103
- typedef
104
- yamlbyte_result_t
105
- (*yamlbyte_push_t)(
106
- yamlbyte_consumer_t self,
107
- yamlbyte_inst_t inst
108
- );
109
-
110
- /* consumer pulls a bytecode instruction from the producer; in this
111
- * case the instruction (and is buffer) are owned by the producer and
112
- * will remain valid till the pull function is called once again;
113
- * if the instruction is NULL, then there are no more results; and
114
- * it is important to call the pull function till it returns NULL so
115
- * that the producer can clean up its memory allocations */
116
- typedef
117
- yamlbyte_result_t
118
- (*yamlbyte_pull_t)(
119
- yamlbyte_producer_t self,
120
- yamlbyte_inst_t *inst /* to be filled in by the producer */
121
- );
122
-
123
- /*
124
- * Buffer based API
125
- */
126
-
127
- /* producer pushes a null terminated buffer filled with one or more
128
- * bytecode events to the consumer; if the consumer's result is not
129
- * YAMLBYTE_OK, then the producer should stop */
130
- typedef
131
- yamlbyte_result_t
132
- (*yamlbyte_pushbuff_t)(
133
- yamlbyte_consumer_t self,
134
- yamlbyte_buff_t buff
135
- );
136
-
137
- /* consumer pulls bytecode events from the producer; in this case
138
- * the buffer is owned by the producer, and will remain valid till
139
- * the pull function is called once again; if the buffer pointer
140
- * is set to NULL, then there are no more results; it is important
141
- * to call the pull function till it returns NULL so that the
142
- * producer can clean up its memory allocations */
143
- typedef
144
- yamlbyte_result_t
145
- (*yamlbyte_pullbuff_t)(
146
- yamlbyte_producer_t self,
147
- yamlbyte_buff_t *buff /* to be filled in by the producer */
148
- );
149
-
150
- /* convert a pull interface to a push interface; the reverse process
151
- * requires threads and thus is language dependent */
152
- #define YAMLBYTE_PULL2PUSH(pull,producer,push,consumer,result) \
153
- do { \
154
- yamlbyte_pullbuff_t _pull = (pull); \
155
- yamlbyte_pushbuff_t _push = (push); \
156
- yamlbyte_result_t _result = YAMLBYTE_OK; \
157
- yamlbyte_producer_t _producer = (producer); \
158
- yamlbyte_consumer_t _consumer = (consumer); \
159
- while(1) { \
160
- yamlbyte_buff_t buff = NULL; \
161
- _result = _pull(_producer,&buff); \
162
- if(YAMLBYTE_OK != result || NULL == buff) \
163
- break; \
164
- _result = _push(_consumer,buff); \
165
- if(YAMLBYTE_OK != result) \
166
- break; \
167
- } \
168
- (result) = _result; \
169
- } while(0)
170
-
171
- #endif