sproutcore 0.9.10 → 0.9.11
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/History.txt +25 -0
- data/Manifest.txt +2 -1
- data/bin/sc-server +4 -1
- data/frameworks/sproutcore/HISTORY +75 -3
- data/frameworks/sproutcore/{Core.js → core.js} +2 -3
- data/frameworks/sproutcore/drag/drag.js +1 -1
- data/frameworks/sproutcore/english.lproj/buttons.css +3 -2
- data/frameworks/sproutcore/english.lproj/detect-browser +44 -0
- data/frameworks/sproutcore/foundation/animator.js +40 -40
- data/frameworks/sproutcore/foundation/application.js +1 -1
- data/frameworks/sproutcore/foundation/benchmark.js +2 -2
- data/frameworks/sproutcore/foundation/error.js +61 -16
- data/frameworks/sproutcore/foundation/input_manager.js +1 -1
- data/frameworks/sproutcore/foundation/mock.js +1 -1
- data/frameworks/sproutcore/foundation/node_descriptor.js +2 -2
- data/frameworks/sproutcore/foundation/object.js +1 -1
- data/frameworks/sproutcore/foundation/path_module.js +1 -1
- data/frameworks/sproutcore/foundation/responder.js +1 -1
- data/frameworks/sproutcore/foundation/run_loop.js +1 -1
- data/frameworks/sproutcore/foundation/server.js +1 -1
- data/frameworks/sproutcore/foundation/string.js +32 -3
- data/frameworks/sproutcore/foundation/timer.js +1 -1
- data/frameworks/sproutcore/foundation/undo_manager.js +1 -1
- data/frameworks/sproutcore/globals/window.js +1 -1
- data/frameworks/sproutcore/lib/core_views.rb +1 -1
- data/frameworks/sproutcore/lib/index.rhtml +7 -1
- data/frameworks/sproutcore/mixins/observable.js +115 -7
- data/frameworks/sproutcore/models/record.js +1 -1
- data/frameworks/sproutcore/tests/views/view/innerFrame.rhtml +101 -99
- data/frameworks/sproutcore/views/collection/grid.js +1 -1
- data/frameworks/sproutcore/views/collection/table.js +1 -1
- data/frameworks/sproutcore/views/list_item.js +1 -1
- data/frameworks/sproutcore/views/progress.js +21 -11
- data/frameworks/sproutcore/views/segmented.js +40 -15
- data/lib/sproutcore/bundle.rb +3 -3
- data/lib/sproutcore/bundle_manifest.rb +7 -7
- data/lib/sproutcore/jsdoc.rb +4 -2
- data/lib/sproutcore/library.rb +112 -43
- data/lib/sproutcore/merb/bundle_controller.rb +77 -4
- data/lib/sproutcore/version.rb +1 -1
- data/sc_generators/client/templates/core.js +1 -4
- metadata +4 -3
@@ -593,7 +593,7 @@ SC.Record.Date = function(value,direction) {
|
|
593
593
|
} else if (typeof(value) == "string") {
|
594
594
|
// try to parse date. trim any decimal numbers at end since Rails sends
|
595
595
|
// this sometimes.
|
596
|
-
var ret = Date.
|
596
|
+
var ret = Date.parse(value.replace(/\.\d+$/,'')) ;
|
597
597
|
if (ret) value = ret ;
|
598
598
|
}
|
599
599
|
return value ;
|
@@ -10,91 +10,91 @@
|
|
10
10
|
|
11
11
|
<script>
|
12
12
|
|
13
|
-
Test.context("CASE 1: Auto-layout view with no padding & no border", {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
});
|
46
|
-
|
47
|
-
|
48
|
-
// CASE 2: Auto-layout of view with padding
|
49
|
-
// - same as Case 1, except innerFrame = frame less padding
|
50
|
-
CASE2_OFFSET = 2;
|
51
|
-
Test.context("CASE 2: Auto-layout view with padding & border", {
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
});
|
13
|
+
// Test.context("CASE 1: Auto-layout view with no padding & no border", {
|
14
|
+
//
|
15
|
+
// // IMPORTANT: This test validates that innerFrame works even on elements in
|
16
|
+
// // IE when hasLayout = false. Make sure you do not edit the CSS or other
|
17
|
+
// // properties for this test in such a way that it would give the test
|
18
|
+
// // element hasLayout.
|
19
|
+
// "frame should == innerFrame": function() {
|
20
|
+
//
|
21
|
+
// // verify hasLayout is false in IE.
|
22
|
+
// if (SC.Platform.IE) {
|
23
|
+
// var hasLayout = v.rootElement.currentStyle.hasLayout ;
|
24
|
+
// assertEqual(hasLayout, false, 'element.hasLayout MUST be false in IE');
|
25
|
+
// }
|
26
|
+
//
|
27
|
+
// SC.rectsEqual(v.get('frame'), v.get('innerFrame')).shouldEqual(true) ;
|
28
|
+
// },
|
29
|
+
//
|
30
|
+
// "frame & innerFrame should change when CSS changed and viewFrameDidChange() is called": function() {
|
31
|
+
// var origFrame = this.v.get('frame') ;
|
32
|
+
// var origInnerFrame = this.v.get('innerFrame') ;
|
33
|
+
//
|
34
|
+
// this.v.addClassName('half') ;
|
35
|
+
// this.v.viewFrameDidChange() ;
|
36
|
+
//
|
37
|
+
// SC.rectsEqual(this.v.get('frame'), origFrame).shouldEqual(false) ;
|
38
|
+
// SC.rectsEqual(this.v.get('innerFrame'), origInnerFrame).shouldEqual(false) ;
|
39
|
+
//
|
40
|
+
// this.v.removeClassName('half') ; //reset
|
41
|
+
// },
|
42
|
+
//
|
43
|
+
// setup: function() { this.v = SC.page.get('case1'); }
|
44
|
+
//
|
45
|
+
// });
|
46
|
+
//
|
47
|
+
//
|
48
|
+
// // CASE 2: Auto-layout of view with padding
|
49
|
+
// // - same as Case 1, except innerFrame = frame less padding
|
50
|
+
// CASE2_OFFSET = 2;
|
51
|
+
// Test.context("CASE 2: Auto-layout view with padding & border", {
|
52
|
+
//
|
53
|
+
// "innerFrame should == frame less border": function() {
|
54
|
+
// var f = v.get('frame') ;
|
55
|
+
// f.x += CASE2_OFFSET; f.y += CASE2_OFFSET;
|
56
|
+
// f.width -= (CASE2_OFFSET*2); f.height -= (CASE2_OFFSET*2) ;
|
57
|
+
// //console.log('f: %@ if: %@'.fmt($H(f).inspect(), $H(this.v.get('innerFrame')).inspect()));
|
58
|
+
//
|
59
|
+
// SC.rectsEqual(f, v.get('innerFrame')).shouldEqual(true) ;
|
60
|
+
// },
|
61
|
+
//
|
62
|
+
// "frame & innerFrame should change when CSS changed and viewFrameDidChange is called.; innerFrame should maintain proportion": function() {
|
63
|
+
// var origFrame = v.get('frame') ;
|
64
|
+
// var origInnerFrame = v.get('innerFrame') ;
|
65
|
+
//
|
66
|
+
// // verify that frames change
|
67
|
+
// v.addClassName('half') ;
|
68
|
+
// v.viewFrameDidChange() ;
|
69
|
+
//
|
70
|
+
// SC.rectsEqual(v.get('frame'), origFrame).shouldEqual(false) ;
|
71
|
+
// SC.rectsEqual(v.get('innerFrame'), origInnerFrame).shouldEqual(false) ;
|
72
|
+
//
|
73
|
+
// // verify that innerFrame changes correctly.
|
74
|
+
// var f = this.v.get('frame') ;
|
75
|
+
// f.x += CASE2_OFFSET; f.y += CASE2_OFFSET;
|
76
|
+
// f.width -= (CASE2_OFFSET*2); f.height -= (CASE2_OFFSET*2) ;
|
77
|
+
// SC.rectsEqual(f, v.get('innerFrame')).shouldEqual(true) ;
|
78
|
+
//
|
79
|
+
// v.removeClassName('half') ;
|
80
|
+
//
|
81
|
+
// },
|
82
|
+
//
|
83
|
+
// "changing border should change innerFrame": function() {
|
84
|
+
// v.setStyle({ border: '5px red solid' }) ;
|
85
|
+
// v.viewFrameDidChange() ;
|
86
|
+
// var f = v.get('frame') ;
|
87
|
+
// f.x += 5; f.y += 5;
|
88
|
+
// f.width -= 10; f.height -= 10 ;
|
89
|
+
//
|
90
|
+
// var got = v.get('innerFrame') ;
|
91
|
+
// console.log('expected: %@ got: %@'.fmt($H(f).inspect(), $H(got).inspect()));
|
92
|
+
// SC.rectsEqual(f, got).shouldEqual(true) ;
|
93
|
+
// },
|
94
|
+
//
|
95
|
+
// setup: function() { this.v = SC.page.get('case2'); }
|
96
|
+
//
|
97
|
+
// });
|
98
98
|
|
99
99
|
// CASE 3: Manual-layout view with some padding and border
|
100
100
|
// - innerFrame should update at frame is changed.
|
@@ -104,12 +104,12 @@ CASE3_OFFSET_THICK = 5 ;
|
|
104
104
|
Test.context("CASE 3: manual-layout view with some padding and no border", {
|
105
105
|
|
106
106
|
"innerFrame should == frame less border": function() {
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
107
|
+
var f = this.v.get('frame') ;
|
108
|
+
f.x += CASE3_OFFSET; f.y += CASE3_OFFSET;
|
109
|
+
f.width -= (CASE3_OFFSET*2); f.height -= (CASE3_OFFSET*2) ;
|
110
|
+
|
111
|
+
SC.rectsEqual(f, this.v.get('innerFrame')).shouldEqual(true) ;
|
112
|
+
},
|
113
113
|
|
114
114
|
"after frame change, innerFrame should == frame less border, viewFrameDidChange is not required.": function() {
|
115
115
|
var f ;
|
@@ -117,16 +117,18 @@ Test.context("CASE 3: manual-layout view with some padding and no border", {
|
|
117
117
|
this.v.set('frame', f = { width: 50, x: 10, y: 10, height: 50 }) ;
|
118
118
|
f.x += CASE3_OFFSET; f.y += CASE3_OFFSET;
|
119
119
|
f.width -= (CASE3_OFFSET*2); f.height -= (CASE3_OFFSET*2) ;
|
120
|
-
|
120
|
+
|
121
|
+
console.log('f: %@ if: %@'.fmt($H(f).inspect(), $H(this.v.get('innerFrame')).inspect()));
|
122
|
+
console.log('v=%@'.fmt(v.rootElement)) ;
|
121
123
|
|
122
124
|
SC.rectsEqual(f, this.v.get('innerFrame')).shouldEqual(true) ;
|
123
125
|
},
|
124
|
-
|
126
|
+
|
125
127
|
"changing CSS to change width should not impact innerFrame if viewFrameDidChange is called": function() {
|
126
128
|
var f = this.v.get('frame') ;
|
127
129
|
f.x += CASE3_OFFSET; f.y += CASE3_OFFSET;
|
128
130
|
f.width -= (CASE3_OFFSET*2); f.height -= (CASE3_OFFSET*2) ;
|
129
|
-
|
131
|
+
|
130
132
|
this.v.addClassName('half') ;
|
131
133
|
this.v.viewFrameDidChange() ;
|
132
134
|
|
@@ -134,20 +136,20 @@ Test.context("CASE 3: manual-layout view with some padding and no border", {
|
|
134
136
|
|
135
137
|
SC.rectsEqual(f, this.v.get('innerFrame')).shouldEqual(true) ;
|
136
138
|
},
|
137
|
-
|
139
|
+
|
138
140
|
"changing CSS to change border width should impact innerFrame if viewFrameDidChange() is called": function() {
|
139
|
-
|
141
|
+
|
140
142
|
this.v.get('frame') ;
|
141
143
|
this.v.get('innerFrame'); // make sure the old value is cached.
|
142
144
|
|
143
145
|
this.v.addClassName('thick-border') ;
|
144
146
|
this.v.viewFrameDidChange() ;
|
145
|
-
|
147
|
+
|
146
148
|
// calculate the expected new value
|
147
149
|
var f = this.v.get('frame') ;
|
148
150
|
f.x += CASE3_OFFSET_THICK; f.y += CASE3_OFFSET_THICK;
|
149
151
|
f.width -= (CASE3_OFFSET_THICK*2); f.height -= (CASE3_OFFSET_THICK*2) ;
|
150
|
-
|
152
|
+
|
151
153
|
console.log('f: %@ if: %@'.fmt($H(f).inspect(), $H(this.v.get('innerFrame')).inspect()));
|
152
154
|
|
153
155
|
SC.rectsEqual(f, this.v.get('innerFrame')).shouldEqual(true) ;
|
@@ -275,7 +277,7 @@ main = function() { SC.page.awake(); };
|
|
275
277
|
|
276
278
|
.half { width: 50%; }
|
277
279
|
|
278
|
-
.case2, .case3 { padding: 10px; border: 2px blue solid; }
|
280
|
+
.case2, .case3 { padding: 10px; border: 2px blue solid; position: absolute; }
|
279
281
|
|
280
282
|
.case.thick-border { border: 5px green solid; }
|
281
283
|
|
@@ -74,7 +74,7 @@ SC.GridView = SC.CollectionView.extend(
|
|
74
74
|
},
|
75
75
|
|
76
76
|
layoutItemView: function(itemView, contentIndex, firstLayout) {
|
77
|
-
if (!itemView) debugger ;
|
77
|
+
//if (!itemView) debugger ;
|
78
78
|
SC.Benchmark.start('SC.GridView.layoutItemViewsFor') ;
|
79
79
|
|
80
80
|
var rowHeight = this.get('rowHeight') || 0 ;
|
@@ -50,7 +50,7 @@ SC.TableView = SC.CollectionView.extend(
|
|
50
50
|
|
51
51
|
/** @private */
|
52
52
|
layoutItemView: function(itemView, contentIndex, firstLayout) {
|
53
|
-
if (!itemView) debugger ;
|
53
|
+
//if (!itemView) debugger ;
|
54
54
|
SC.Benchmark.start('SC.TableView.layoutItemViewsFor') ;
|
55
55
|
|
56
56
|
var rowHeight = this.get('rowHeight') || 0 ;
|
@@ -20,8 +20,8 @@ require('views/view') ;
|
|
20
20
|
Style the progress bar with the following CSS classes:
|
21
21
|
|
22
22
|
.progress.indeterminate = to show an indeterminate progress. inner will
|
23
|
-
|
24
|
-
.progress.disabled = show as disabled.
|
23
|
+
be set to fill 100%
|
24
|
+
.progress.disabled = show as disabled.
|
25
25
|
|
26
26
|
@extends SC.View
|
27
27
|
*/
|
@@ -41,14 +41,19 @@ SC.ProgressView = SC.View.extend({
|
|
41
41
|
*/
|
42
42
|
maximum: 1.0,
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
/**
|
45
|
+
Bind this to the current value of the progress bar. Note that by default an
|
46
|
+
empty value will disable the progress bar and a multiple value too make it
|
47
|
+
indeterminate.
|
48
|
+
*/
|
47
49
|
value: 0.50,
|
48
50
|
valueBindingDefault: SC.Binding.SingleNotEmpty,
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
+
/**
|
53
|
+
Set to true if the item in progress is indeterminate. This may be overridden
|
54
|
+
by the actual value.
|
55
|
+
@returns {Boolean}
|
56
|
+
*/
|
52
57
|
isIndeterminate: function(key,value) {
|
53
58
|
if (value !== undefined) {
|
54
59
|
this._isIndeterminate = value ;
|
@@ -56,7 +61,10 @@ SC.ProgressView = SC.View.extend({
|
|
56
61
|
return this._isIndeterminate && (this.value != SC.Binding.EMPTY_PLACEHOLDER) ;
|
57
62
|
}.property(),
|
58
63
|
|
59
|
-
|
64
|
+
/**
|
65
|
+
Set to false to disable the progress bar.
|
66
|
+
@returns {Boolean}
|
67
|
+
*/
|
60
68
|
isEnabled: function(key, value) {
|
61
69
|
if (value !== undefined) {
|
62
70
|
this._isEnabled = value ;
|
@@ -88,11 +96,13 @@ SC.ProgressView = SC.View.extend({
|
|
88
96
|
Element.setClassName(this,'indeterminate',isIndeterminate) ;
|
89
97
|
Element.setClassName(this,'disabled',!isEnabled) ;
|
90
98
|
|
91
|
-
// compute value
|
99
|
+
// compute value for setting the width of the inner progress
|
92
100
|
var value ;
|
93
|
-
if (
|
101
|
+
if (!isEnabled) {
|
94
102
|
value = 0.0 ;
|
95
|
-
} else {
|
103
|
+
} else if (isIndeterminate) {
|
104
|
+
value = 1.0;
|
105
|
+
}else {
|
96
106
|
var minimum = this.get('minimum') || 0.0 ;
|
97
107
|
var maximum = this.get('maximum') || 1.0 ;
|
98
108
|
value = this.get('value') || 0.0 ;
|
@@ -5,27 +5,52 @@
|
|
5
5
|
|
6
6
|
require('views/view') ;
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
// want to reflect and a button with the matching name 'xxxButton' will have
|
11
|
-
// its select state set.
|
12
|
-
//
|
13
|
-
// Also, on configure, if the buttons in the segment view do not have actions
|
14
|
-
// set, then the button will be configured to change the select state.
|
15
|
-
SC.SegmentedView = SC.View.extend({
|
8
|
+
/**
|
9
|
+
@class
|
16
10
|
|
17
|
-
|
11
|
+
SegmentView shows an array of buttons that can have mutually exclusive
|
12
|
+
select states. You can change the value property to the state you
|
13
|
+
want to reflect and a button with the matching name 'xxxButton' will have
|
14
|
+
its select state set.
|
18
15
|
|
19
|
-
|
16
|
+
Also, on configure, if the buttons in the segment view do not have actions
|
17
|
+
set, then the button will be configured to change the select state.
|
18
|
+
|
19
|
+
@extends SC.View
|
20
|
+
@since SproutCore 1.0
|
21
|
+
*/
|
22
|
+
SC.SegmentedView = SC.View.extend(
|
23
|
+
/** @scope SC.SegmentedView.prototype */ {
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
/**
|
26
|
+
The value of the segmented view.
|
27
|
+
|
28
|
+
The SegmentedView's value will always be the value of the currently
|
29
|
+
selected button. Setting this value will change the selected button.
|
30
|
+
If you set this value to something that has no matching button, then
|
31
|
+
no buttons will be selected.
|
32
|
+
|
33
|
+
@field {Object}
|
34
|
+
*/
|
35
|
+
value: null,
|
36
|
+
|
37
|
+
/**
|
38
|
+
Contains an array of buttons after init.
|
39
|
+
*/
|
40
|
+
segments: null,
|
41
|
+
|
42
|
+
/**
|
43
|
+
Set to YES to enabled the segmented view, NO to disabled it.
|
44
|
+
*/
|
23
45
|
isEnabled: true,
|
24
|
-
|
25
|
-
|
26
|
-
|
46
|
+
|
47
|
+
/**
|
48
|
+
If YES, clicking a selected button again will deselect it, setting the
|
49
|
+
segmented views value to null. Defaults to NO.
|
50
|
+
*/
|
27
51
|
allowsEmptySelection: false,
|
28
52
|
|
53
|
+
/** @private */
|
29
54
|
init: function() {
|
30
55
|
arguments.callee.base.call(this) ;
|
31
56
|
|
data/lib/sproutcore/bundle.rb
CHANGED
@@ -453,9 +453,9 @@ module SproutCore
|
|
453
453
|
|
454
454
|
|
455
455
|
# OK, looks like this is ready to be built.
|
456
|
-
if entry
|
457
|
-
|
458
|
-
|
456
|
+
# if the entry is served directly from source
|
457
|
+
if entry.use_source_directly?
|
458
|
+
SC.logger.debug("~ No Build Required: #{entry.filename} (will be served directly)")
|
459
459
|
else
|
460
460
|
SC.logger.debug("~ Building #{entry.type.to_s.capitalize}: #{entry.filename}")
|
461
461
|
BuildTools.send("build_#{entry.type}".to_sym, entry, self)
|
@@ -254,17 +254,17 @@ module SproutCore
|
|
254
254
|
# efficient than doing it later.
|
255
255
|
url_root = (src_path == 'index.html') ? bundle.index_root : bundle.url_root
|
256
256
|
cache_link = nil
|
257
|
-
|
257
|
+
use_source_directly =false
|
258
258
|
|
259
259
|
# Note: you can only access real resources via the cache. If the entry
|
260
260
|
# is a composite then do not go through cache.
|
261
261
|
if (self.build_mode == :development) && composite.nil?
|
262
262
|
cache_link = '_cache' if CACHED_TYPES.include?(src_type)
|
263
|
-
|
263
|
+
use_source_directly = true if SYMLINKED_TYPES.include?(src_type)
|
264
264
|
end
|
265
265
|
|
266
|
-
ret.
|
267
|
-
if
|
266
|
+
ret.use_source_directly = use_source_directly
|
267
|
+
if use_source_directly
|
268
268
|
ret.build_path = File.join(bundle.build_root, '_src', src_path)
|
269
269
|
ret.url = [url_root, '_src', src_path].join('/')
|
270
270
|
else
|
@@ -301,11 +301,11 @@ module SproutCore
|
|
301
301
|
# type:: the top-level category
|
302
302
|
# original_path:: save the original path used to build this entry
|
303
303
|
# hidden:: if true, this entry is needed internally, but otherwise should not be used
|
304
|
-
#
|
304
|
+
# use_source_directly:: if true, then this entry should be handled via the build symlink
|
305
305
|
# language:: the language in use when this entry was created
|
306
306
|
# composite:: If set, this will contain the filenames of other resources that should be combined to form this resource.
|
307
307
|
#
|
308
|
-
class ManifestEntry < Struct.new(:filename, :ext, :source_path, :url, :build_path, :type, :original_path, :hidden, :
|
308
|
+
class ManifestEntry < Struct.new(:filename, :ext, :source_path, :url, :build_path, :type, :original_path, :hidden, :use_source_directly, :language, :composite)
|
309
309
|
def to_hash
|
310
310
|
ret = {}
|
311
311
|
self.members.zip(self.values).each { |p| ret[p[0]] = p[1] }
|
@@ -313,7 +313,7 @@ module SproutCore
|
|
313
313
|
end
|
314
314
|
|
315
315
|
def hidden?; !!hidden; end
|
316
|
-
def
|
316
|
+
def use_source_directly?; !!use_source_directly; end
|
317
317
|
def composite?; !!composite; end
|
318
318
|
|
319
319
|
def localized?; !!source_path.match(/\.lproj/); end
|