Commit 05dc0664a7d196eeab3f1883b04f917b5b944013
Merge branch 'i18n-reload' of https://github.com/gmarcos87/patchwork into gmarcos87-i18n-reload
Matt McKegg committed on 9/30/2017, 2:44:36 AMParent: 0e7c0899affc876a69d822ae746c7c358f8c2906
Parent: db20e2ee1a31d6f09639c346e37894ebd770f6b3
Files changed
index.js | ||
---|---|---|
@@ -18,8 +18,9 @@ | ||
18 | 18 | } |
19 | 19 | var ssbConfig = null |
20 | 20 | var quitting = false |
21 | 21 | |
22 | + | |
22 | 23 | electron.app.on('ready', () => { |
23 | 24 | setupContext('ssb', { |
24 | 25 | server: !(process.argv.includes('-g') || process.argv.includes('--use-global-ssb')) |
25 | 26 | }, () => { |
@@ -79,9 +80,9 @@ | ||
79 | 80 | width: windowState.width, |
80 | 81 | height: windowState.height, |
81 | 82 | titleBarStyle: 'hidden-inset', |
82 | 83 | autoHideMenuBar: true, |
83 | - title: 'Patchwork', | |
84 | + title: "Patchwork", | |
84 | 85 | show: true, |
85 | 86 | backgroundColor: '#EEE', |
86 | 87 | webPreferences: { |
87 | 88 | experimentalFeatures: true |
lib/window.js | ||
---|---|---|
@@ -18,9 +18,9 @@ | ||
18 | 18 | electron.webFrame.setZoomLevelLimits(1, 1) |
19 | 19 | |
20 | 20 | var config = ${JSON.stringify(config)} |
21 | 21 | var data = ${JSON.stringify(opts.data)} |
22 | - var title = ${JSON.stringify(opts.title || 'Patchwork')} | |
22 | + var title = ${JSON.stringify(opts.title || "Patchwork" )} | |
23 | 23 | |
24 | 24 | document.documentElement.querySelector('head').appendChild( |
25 | 25 | h('title', title) |
26 | 26 | ) |
main-window.js | ||
---|---|---|
@@ -12,8 +12,9 @@ | ||
12 | 12 | var ref = require('ssb-ref') |
13 | 13 | var setupContextMenuAndSpellCheck = require('./lib/context-menu-and-spellcheck') |
14 | 14 | var watch = require('mutant/watch') |
15 | 15 | |
16 | + | |
16 | 17 | module.exports = function (config) { |
17 | 18 | var sockets = combine( |
18 | 19 | overrideConfig(config), |
19 | 20 | addCommand('app.navigate', setView), |
@@ -39,16 +40,19 @@ | ||
39 | 40 | 'profile.sheet.edit': 'first', |
40 | 41 | 'app.navigate': 'first', |
41 | 42 | 'channel.obs.subscribed': 'first', |
42 | 43 | 'settings.obs.get': 'first', |
44 | + 'intl.sync.i18n': 'first', | |
43 | 45 | })) |
44 | 46 | |
45 | 47 | setupContextMenuAndSpellCheck(api.config.sync.load()) |
48 | + | |
49 | + const i18n = api.intl.sync.i18n | |
46 | 50 | |
47 | 51 | var id = api.keys.sync.id() |
48 | 52 | var latestUpdate = LatestUpdate() |
49 | 53 | var subscribedChannels = api.channel.obs.subscribed(id) |
50 | - | |
54 | + | |
51 | 55 | // prompt to setup profile on first use |
52 | 56 | onceTrue(api.sbot.obs.connection, (sbot) => { |
53 | 57 | sbot.latestSequence(sbot.id, (_, key) => { |
54 | 58 | if (key == null) { |
@@ -90,32 +94,32 @@ | ||
90 | 94 | classList: [ when(views.canGoForward, '-active') ] |
91 | 95 | }) |
92 | 96 | ]), |
93 | 97 | h('span.nav', [ |
94 | - tab('Public', '/public'), | |
95 | - tab('Private', '/private'), | |
96 | - dropTab('More', [ | |
98 | + tab(i18n("Public"), '/public'), | |
99 | + tab(i18n("Private"), '/private'), | |
100 | + dropTab(i18n('More'), [ | |
97 | 101 | getSubscribedChannelMenu, |
98 | - ['Gatherings', '/gatherings'], | |
99 | - ['Extended Network', '/all'], | |
102 | + [i18n('Gatherings'), '/gatherings'], | |
103 | + [i18n('Extended Network'), '/all'], | |
100 | 104 | {separator: true}, |
101 | - ['Settings', '/settings'] | |
105 | + [i18n('Settings'), '/settings'] | |
102 | 106 | ]) |
103 | 107 | ]), |
104 | 108 | h('span.appTitle', [ |
105 | - h('span.title', 'Patchwork'), | |
109 | + h('span.title', i18n("Patchwork")), | |
106 | 110 | api.app.html.progressNotifier() |
107 | 111 | ]), |
108 | 112 | h('span', [ api.app.html.search(api.app.navigate) ]), |
109 | 113 | h('span.nav', [ |
110 | - tab('Profile', id), | |
111 | - tab('Mentions', '/mentions') | |
114 | + tab(i18n('Profile'), id), | |
115 | + tab(i18n('Mentions'), '/mentions') | |
112 | 116 | ]) |
113 | 117 | ]), |
114 | 118 | when(latestUpdate, |
115 | 119 | h('div.info', [ |
116 | 120 | h('a.message -update', { href: 'https://github.com/ssbc/patchwork/releases' }, [ |
117 | - h('strong', ['Patchwork ', latestUpdate, ' has been released.']), ' Click here to download and view more info!', | |
121 | + h('strong', ['Patchwork ', latestUpdate, i18n(' has been released.')]), i18n(' Click here to download and view more info!'), | |
118 | 122 | h('a.ignore', {'ev-click': latestUpdate.ignore}, 'X') |
119 | 123 | ]) |
120 | 124 | ]) |
121 | 125 | ), |
@@ -149,11 +153,11 @@ | ||
149 | 153 | var channels = Array.from(subscribedChannels()).sort(localeCompare) |
150 | 154 | |
151 | 155 | if (channels.length) { |
152 | 156 | return { |
153 | - label: 'Channels', | |
157 | + label: i18n('Channels'), | |
154 | 158 | submenu: [ |
155 | - { label: 'Browse All', | |
159 | + { label: i18n('Browse All'), | |
156 | 160 | click () { |
157 | 161 | setView('/channels') |
158 | 162 | } |
159 | 163 | }, |
@@ -168,9 +172,9 @@ | ||
168 | 172 | })) |
169 | 173 | } |
170 | 174 | } else { |
171 | 175 | return { |
172 | - label: 'Browse Channels', | |
176 | + label: i18n('Browse Channels'), | |
173 | 177 | click () { |
174 | 178 | setView('/channels') |
175 | 179 | } |
176 | 180 | } |
modules/app/html/progress-notifier.js | ||
---|---|---|
@@ -1,8 +1,8 @@ | ||
1 | 1 | var {computed, when, h, Value} = require('mutant') |
2 | 2 | var nest = require('depnest') |
3 | 3 | var sustained = require('../../../lib/sustained') |
4 | -var pull = require('pull-stream') | |
4 | +const pull = require('pull-stream') | |
5 | 5 | |
6 | 6 | exports.gives = nest('app.html.progressNotifier') |
7 | 7 | |
8 | 8 | exports.needs = nest({ |
@@ -11,12 +11,14 @@ | ||
11 | 11 | 'progress.obs': { |
12 | 12 | indexes: 'first', |
13 | 13 | replicate: 'first', |
14 | 14 | migration: 'first' |
15 | - } | |
15 | + }, | |
16 | + 'intl.sync.i18n':'first' | |
16 | 17 | }) |
17 | 18 | |
18 | 19 | exports.create = function (api) { |
20 | + const i18n = api.intl.sync.i18n | |
19 | 21 | return nest('app.html.progressNotifier', function (id) { |
20 | 22 | var replicateProgress = api.progress.obs.replicate() |
21 | 23 | var indexes = api.progress.obs.indexes() |
22 | 24 | var migration = api.progress.obs.migration() |
@@ -45,17 +47,15 @@ | ||
45 | 47 | |
46 | 48 | return h('div.info', { hidden }, [ |
47 | 49 | h('div.status', [ |
48 | 50 | when(displaying, h('Loading -small', [ |
49 | - when(waiting, 'Waiting for Scuttlebot...', | |
50 | - when(pendingMigration, | |
51 | - [h('span.info', 'Upgrading database'), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: migrationProgress })], | |
52 | - when(computed(replicateProgress.incompleteFeeds, (v) => v > 5), | |
53 | - [h('span.info', 'Downloading new messages'), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: downloadProgress })], | |
54 | - when(pending, [ | |
55 | - [h('span.info', 'Indexing database'), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: indexProgress })] | |
56 | - ], 'Scuttling...') | |
57 | - ) | |
51 | + when(pendingMigration, | |
52 | + [h('span.info', i18n('Upgrading database')), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: migrationProgress })], | |
53 | + when(computed(replicateProgress.incompleteFeeds, (v) => v > 5), | |
54 | + [h('span.info', i18n('Downloading new messages')), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: downloadProgress })], | |
55 | + when(pending, [ | |
56 | + [h('span.info', i18n('Indexing database')), h('progress', { style: {'margin-left': '10px'}, min: 0, max: 1, value: indexProgress })] | |
57 | + ], i18n('Scuttling...')) | |
58 | 58 | ) |
59 | 59 | ) |
60 | 60 | ])) |
61 | 61 | ]) |
modules/app/html/search.js | ||
---|---|---|
@@ -1,24 +1,28 @@ | ||
1 | 1 | var h = require('mutant/h') |
2 | 2 | var nest = require('depnest') |
3 | 3 | var addSuggest = require('suggest-box') |
4 | 4 | |
5 | +var appRoot = require('app-root-path'); | |
6 | + | |
5 | 7 | exports.needs = nest({ |
6 | 8 | 'profile.async.suggest': 'first', |
7 | - 'channel.async.suggest': 'first' | |
9 | + 'channel.async.suggest': 'first', | |
10 | + 'intl.sync.i18n': 'first' | |
8 | 11 | }) |
9 | 12 | |
10 | 13 | exports.gives = nest('app.html.search') |
11 | 14 | |
12 | 15 | var pages = ['/public', '/private', '/mentions', '/all', '/gatherings'] |
13 | 16 | |
14 | 17 | exports.create = function (api) { |
18 | + const i18n = api.intl.sync.i18n | |
15 | 19 | return nest('app.html.search', function (setView) { |
16 | 20 | var getProfileSuggestions = api.profile.async.suggest() |
17 | 21 | var getChannelSuggestions = api.channel.async.suggest() |
18 | 22 | var searchBox = h('input.search', { |
19 | 23 | type: 'search', |
20 | - placeholder: 'word, @key, #channel', | |
24 | + placeholder: i18n('word, @key, #channel'), | |
21 | 25 | 'ev-suggestselect': (ev) => { |
22 | 26 | setView(ev.detail.id) |
23 | 27 | searchBox.value = ev.detail.id |
24 | 28 | }, |
modules/feed/html/rollup.js | ||
---|---|---|
@@ -17,8 +17,9 @@ | ||
17 | 17 | |
18 | 18 | // bump even for first message |
19 | 19 | var rootBumpTypes = ['mention', 'channel-mention'] |
20 | 20 | |
21 | + | |
21 | 22 | exports.needs = nest({ |
22 | 23 | 'about.obs.name': 'first', |
23 | 24 | 'app.sync.externalHandler': 'first', |
24 | 25 | 'message.html.render': 'first', |
@@ -26,16 +27,18 @@ | ||
26 | 27 | 'message.html.link': 'first', |
27 | 28 | 'message.sync.root': 'first', |
28 | 29 | 'feed.pull.rollup': 'first', |
29 | 30 | 'sbot.async.get': 'first', |
30 | - 'keys.sync.id': 'first' | |
31 | + 'keys.sync.id': 'first', | |
32 | + 'intl.sync.i18n': 'first', | |
31 | 33 | }) |
32 | 34 | |
33 | 35 | exports.gives = nest({ |
34 | 36 | 'feed.html.rollup': true |
35 | 37 | }) |
36 | 38 | |
37 | 39 | exports.create = function (api) { |
40 | + const i18n = api.intl.sync.i18n | |
38 | 41 | return nest('feed.html.rollup', function (getStream, { |
39 | 42 | prepend, |
40 | 43 | rootFilter = returnTrue, |
41 | 44 | bumpFilter = returnTrue, |
@@ -46,9 +49,9 @@ | ||
46 | 49 | var updates = Value(0) |
47 | 50 | var yourId = api.keys.sync.id() |
48 | 51 | var throttledUpdates = throttle(updates, 200) |
49 | 52 | var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [ |
50 | - 'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, 'update', 'updates') | |
53 | + 'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, i18n('update'), i18n('updates')) | |
51 | 54 | ]) |
52 | 55 | |
53 | 56 | var abortLastFeed = null |
54 | 57 | var content = Value() |
@@ -190,11 +193,11 @@ | ||
190 | 193 | var bumps = lastBumpType === 'vote' |
191 | 194 | ? getLikeAuthors(groupedBumps[lastBumpType]) |
192 | 195 | : getAuthors(groupedBumps[lastBumpType]) |
193 | 196 | |
194 | - var description = bumpMessages[lastBumpType] || 'added changes' | |
197 | + var description = i18n(bumpMessages[lastBumpType] || 'added changes') | |
195 | 198 | meta = h('div.meta', { title: names(bumps) }, [ |
196 | - many(bumps, api.profile.html.person), ' ', description | |
199 | + many(bumps, api.profile.html.person, i18n), ' ', description | |
197 | 200 | ]) |
198 | 201 | } |
199 | 202 | |
200 | 203 | return h('FeedEvent -post', { |
@@ -205,9 +208,9 @@ | ||
205 | 208 | meta, |
206 | 209 | renderedMessage, |
207 | 210 | when(replyElements.length, [ |
208 | 211 | when(replies.length > replyElements.length || partial, |
209 | - h('a.full', {href: item.key}, ['View full thread (', replies.length, ')']) | |
212 | + h('a.full', {href: item.key}, [i18n('View full thread') +' (', replies.length, ')']) | |
210 | 213 | ), |
211 | 214 | h('div.replies', replyElements) |
212 | 215 | ]) |
213 | 216 | ]) |
@@ -244,36 +247,36 @@ | ||
244 | 247 | } |
245 | 248 | }) |
246 | 249 | } |
247 | 250 | |
248 | -function many (ids, fn) { | |
251 | +function many (ids, fn, intl) { | |
249 | 252 | ids = Array.from(ids) |
250 | 253 | var featuredIds = ids.slice(0, 4) |
251 | 254 | |
252 | 255 | if (ids.length) { |
253 | 256 | if (ids.length > 4) { |
254 | 257 | return [ |
255 | 258 | fn(featuredIds[0]), ', ', |
256 | 259 | fn(featuredIds[1]), ', ', |
257 | - fn(featuredIds[2]), ' and ', | |
258 | - ids.length - 3, ' others' | |
260 | + fn(featuredIds[2]), intl(' and '), | |
261 | + ids.length - 3, intl(' others'), | |
259 | 262 | ] |
260 | 263 | } else if (ids.length === 4) { |
261 | 264 | return [ |
262 | 265 | fn(featuredIds[0]), ', ', |
263 | 266 | fn(featuredIds[1]), ', ', |
264 | - fn(featuredIds[2]), ' and ', | |
267 | + fn(featuredIds[2]), intl(' and '), | |
265 | 268 | fn(featuredIds[3]) |
266 | 269 | ] |
267 | 270 | } else if (ids.length === 3) { |
268 | 271 | return [ |
269 | 272 | fn(featuredIds[0]), ', ', |
270 | - fn(featuredIds[1]), ' and ', | |
273 | + fn(featuredIds[1]), intl(' and '), | |
271 | 274 | fn(featuredIds[2]) |
272 | 275 | ] |
273 | 276 | } else if (ids.length === 2) { |
274 | 277 | return [ |
275 | - fn(featuredIds[0]), ' and ', | |
278 | + fn(featuredIds[0]), intl(' and '), | |
276 | 279 | fn(featuredIds[1]) |
277 | 280 | ] |
278 | 281 | } else { |
279 | 282 | return fn(featuredIds[0]) |
modules/gathering/sheet/edit.js | ||
---|---|---|
@@ -12,12 +12,14 @@ | ||
12 | 12 | 'keys.sync.id': 'first', |
13 | 13 | 'sbot.async.publish': 'first', |
14 | 14 | 'about.obs.latestValue': 'first', |
15 | 15 | 'blob.html.input': 'first', |
16 | - 'blob.sync.url': 'first' | |
16 | + 'blob.sync.url': 'first', | |
17 | + 'intl.sync.i18n': 'first', | |
17 | 18 | }) |
18 | 19 | |
19 | 20 | exports.create = function (api) { |
21 | + const i18n = api.intl.sync.i18n | |
20 | 22 | return nest('gathering.sheet.edit', function (id) { |
21 | 23 | api.sheet.display(close => { |
22 | 24 | var current = id ? { |
23 | 25 | title: api.about.obs.latestValue(id, 'title'), |
@@ -52,44 +54,44 @@ | ||
52 | 54 | h('h2', { |
53 | 55 | style: { |
54 | 56 | 'font-weight': 'normal' |
55 | 57 | } |
56 | - }, [id ? 'Edit' : 'Create', ' Gathering']), | |
58 | + }, [id ? i18n('Edit') : i18n('Create'), i18n(' Gathering')]), | |
57 | 59 | h('GatheringEditor', [ |
58 | 60 | h('input.title', { |
59 | - placeholder: 'Choose a title', | |
61 | + placeholder: i18n('Choose a title'), | |
60 | 62 | hooks: [ValueHook(chosen.title), FocusHook()] |
61 | 63 | }), |
62 | 64 | h('input.date', { |
63 | - placeholder: 'Choose date and time', | |
65 | + placeholder: i18n('Choose date and time'), | |
64 | 66 | hooks: [ |
65 | 67 | PickrHook(chosen.startDateTime) |
66 | 68 | ] |
67 | 69 | }), |
68 | 70 | h('ImageInput .banner', { |
69 | 71 | style: { 'background-image': computed(imageUrl, x => `url(${x})`) } |
70 | 72 | }, [ |
71 | - h('span', ['🖼 Choose Banner Image...']), | |
73 | + h('span', ['🖼 ', i18n('Choose Banner Image...')]), | |
72 | 74 | api.blob.html.input(file => { |
73 | 75 | chosen.image.set(file) |
74 | 76 | }, { |
75 | 77 | accept: 'image/*' |
76 | 78 | }) |
77 | 79 | ]), |
78 | 80 | h('textarea.description', { |
79 | - placeholder: 'Describe the gathering (if you want)', | |
81 | + placeholder: i18n('Describe the gathering (if you want)'), | |
80 | 82 | hooks: [ValueHook(chosen.description)] |
81 | 83 | }) |
82 | 84 | ]) |
83 | 85 | ]), |
84 | 86 | footer: [ |
85 | 87 | h('button -save', { |
86 | 88 | 'ev-click': save, |
87 | 89 | 'disabled': publishing |
88 | - }, when(publishing, 'Publishing...', 'Publish')), | |
90 | + }, when(publishing, i18n('Publishing...'), i18n('Publish'))), | |
89 | 91 | h('button -cancel', { |
90 | 92 | 'ev-click': close |
91 | - }, 'Cancel') | |
93 | + }, i18n('Cancel')) | |
92 | 94 | ] |
93 | 95 | } |
94 | 96 | |
95 | 97 | function ensureExists (cb) { |
@@ -110,9 +112,9 @@ | ||
110 | 112 | var update = {} |
111 | 113 | |
112 | 114 | if (!compareImage(chosen.image(), current.image())) update.image = chosen.image() |
113 | 115 | if (!compareTime(chosen.startDateTime(), current.startDateTime())) update.startDateTime = chosen.startDateTime() |
114 | - if (chosen.title() !== current.title()) update.title = chosen.title() || 'Untitled Gathering' | |
116 | + if (chosen.title() !== current.title()) update.title = chosen.title() || i18n('Untitled Gathering') | |
115 | 117 | if (chosen.description() !== current.description()) update.description = chosen.description() |
116 | 118 | |
117 | 119 | if (Object.keys(update).length) { |
118 | 120 | publishing.set(true) |
@@ -125,11 +127,11 @@ | ||
125 | 127 | if (err) { |
126 | 128 | publishing.set(false) |
127 | 129 | showDialog({ |
128 | 130 | type: 'error', |
129 | - title: 'Error', | |
131 | + title: i18n('Error'), | |
130 | 132 | buttons: ['OK'], |
131 | - message: 'An error occurred while attempting to publish gathering.', | |
133 | + message: i18n('An error occurred while attempting to publish gathering.'), | |
132 | 134 | detail: err.message |
133 | 135 | }) |
134 | 136 | } else { |
135 | 137 | close() |
modules/invite/sheet.js | ||
---|---|---|
@@ -3,14 +3,16 @@ | ||
3 | 3 | var electron = require('electron') |
4 | 4 | |
5 | 5 | exports.needs = nest({ |
6 | 6 | 'sheet.display': 'first', |
7 | - 'invite.async.accept': 'first' | |
7 | + 'invite.async.accept': 'first', | |
8 | + 'intl.sync.i18n': 'first', | |
8 | 9 | }) |
9 | 10 | |
10 | 11 | exports.gives = nest('invite.sheet') |
11 | 12 | |
12 | 13 | exports.create = function (api) { |
14 | + const i18n = api.intl.sync.i18n | |
13 | 15 | return nest('invite.sheet', function () { |
14 | 16 | api.sheet.display(close => { |
15 | 17 | var publishing = Value() |
16 | 18 | var publishStatus = Proxy() |
@@ -20,9 +22,9 @@ | ||
20 | 22 | 'font-size': '200%', |
21 | 23 | 'margin-top': '20px', |
22 | 24 | 'width': '100%' |
23 | 25 | }, |
24 | - placeholder: 'paste invite code here' | |
26 | + placeholder: i18n('paste invite code here') | |
25 | 27 | }) |
26 | 28 | setTimeout(() => { |
27 | 29 | input.focus() |
28 | 30 | input.select() |
@@ -36,11 +38,11 @@ | ||
36 | 38 | h('h2', { |
37 | 39 | style: { |
38 | 40 | 'font-weight': 'normal' |
39 | 41 | } |
40 | - }, ['By default, Patchwork will only see other users that are on the same local area network as you.']), | |
42 | + }, [i18n('By default, Patchwork will only see other users that are on the same local area network as you.')]), | |
41 | 43 | h('div', [ |
42 | - 'In order to share with users on the internet, you need to be invited to a pub server.' | |
44 | + i18n('In order to share with users on the internet, you need to be invited to a pub server.') | |
43 | 45 | ]), |
44 | 46 | input |
45 | 47 | ]), |
46 | 48 | footer: [ |
@@ -52,22 +54,22 @@ | ||
52 | 54 | if (err) { |
53 | 55 | publishing.set(false) |
54 | 56 | showDialog({ |
55 | 57 | type: 'error', |
56 | - title: 'Error', | |
57 | - buttons: ['OK'], | |
58 | - message: 'An error occurred while attempting to redeem invite.', | |
58 | + title: i18n('Error'), | |
59 | + buttons: [i18n('OK')], | |
60 | + message: i18n('An error occurred while attempting to redeem invite.'), | |
59 | 61 | detail: err.message |
60 | 62 | }) |
61 | 63 | } else { |
62 | 64 | close() |
63 | 65 | } |
64 | 66 | })) |
65 | 67 | } |
66 | - }, [ when(publishing, publishStatus, 'Redeem Invite') ]), | |
68 | + }, [ when(publishing, publishStatus, i18n('Redeem Invite')) ]), | |
67 | 69 | h('button -cancel', { |
68 | 70 | 'ev-click': close |
69 | - }, 'Cancel') | |
71 | + }, i18n('Cancel')) | |
70 | 72 | ] |
71 | 73 | } |
72 | 74 | }) |
73 | 75 | }) |
modules/message/async/publish.js | ||
---|---|---|
@@ -4,14 +4,16 @@ | ||
4 | 4 | exports.needs = nest({ |
5 | 5 | 'sheet.display': 'first', |
6 | 6 | 'message.html.render': 'first', |
7 | 7 | 'sbot.async.publish': 'first', |
8 | - 'keys.sync.id': 'first' | |
8 | + 'keys.sync.id': 'first', | |
9 | + 'intl.sync.i18n': 'first', | |
9 | 10 | }) |
10 | 11 | |
11 | 12 | exports.gives = nest('message.async.publish') |
12 | 13 | |
13 | 14 | exports.create = function (api) { |
15 | + const i18n = api.intl.sync.i18n | |
14 | 16 | return nest('message.async.publish', function (content, cb) { |
15 | 17 | api.sheet.display(function (close) { |
16 | 18 | return { |
17 | 19 | content: [ |
@@ -21,10 +23,10 @@ | ||
21 | 23 | author: api.keys.sync.id() |
22 | 24 | }}) |
23 | 25 | ], |
24 | 26 | footer: [ |
25 | - h('button -save', { 'ev-click': publish }, 'Confirm'), | |
26 | - h('button -cancel', { 'ev-click': cancel }, 'Cancel') | |
27 | + h('button -save', { 'ev-click': publish }, i18n('Confirm')), | |
28 | + h('button -cancel', { 'ev-click': cancel }, i18n('Cancel')) | |
27 | 29 | ] |
28 | 30 | } |
29 | 31 | |
30 | 32 | function publish () { |
modules/message/html/backlinks.js | ||
---|---|---|
@@ -7,14 +7,16 @@ | ||
7 | 7 | backlinks: 'first', |
8 | 8 | name: 'first', |
9 | 9 | author: 'first' |
10 | 10 | }, |
11 | - 'profile.html.person': 'first' | |
11 | + 'profile.html.person': 'first', | |
12 | + 'intl.sync.i18n': 'first', | |
12 | 13 | }) |
13 | 14 | |
14 | 15 | exports.gives = nest('message.html.backlinks') |
15 | 16 | |
16 | 17 | exports.create = function (api) { |
18 | + const i18n = api.intl.sync.i18n | |
17 | 19 | return nest('message.html.backlinks', function (msg, {includeReferences = true, includeForks = true} = {}) { |
18 | 20 | if (!ref.type(msg.key)) return [] |
19 | 21 | var backlinks = api.message.obs.backlinks(msg.key) |
20 | 22 | var references = includeReferences ? computed([backlinks, msg], onlyReferences) : [] |
@@ -24,9 +26,9 @@ | ||
24 | 26 | return h('a.backlink', { |
25 | 27 | href: link.id, title: link.id |
26 | 28 | }, [ |
27 | 29 | h('strong', [ |
28 | - api.profile.html.person(link.author), ' forked this discussion:' | |
30 | + api.profile.html.person(link.author), i18n(' forked this discussion:') | |
29 | 31 | ]), ' ', |
30 | 32 | api.message.obs.name(link.id) |
31 | 33 | ]) |
32 | 34 | }), |
@@ -34,9 +36,9 @@ | ||
34 | 36 | return h('a.backlink', { |
35 | 37 | href: link.id, title: link.id |
36 | 38 | }, [ |
37 | 39 | h('strong', [ |
38 | - api.profile.html.person(link.author), ' referenced this message:' | |
40 | + api.profile.html.person(link.author), i18n(' referenced this message:') | |
39 | 41 | ]), ' ', |
40 | 42 | api.message.obs.name(link.id) |
41 | 43 | ]) |
42 | 44 | }) |
modules/message/html/compose.js | ||
---|---|---|
@@ -14,14 +14,16 @@ | ||
14 | 14 | 'profile.async.suggest': 'first', |
15 | 15 | 'channel.async.suggest': 'first', |
16 | 16 | 'message.async.publish': 'first', |
17 | 17 | 'emoji.sync.names': 'first', |
18 | - 'emoji.sync.url': 'first' | |
18 | + 'emoji.sync.url': 'first', | |
19 | + 'intl.sync.i18n': 'first', | |
19 | 20 | }) |
20 | 21 | |
21 | 22 | exports.gives = nest('message.html.compose') |
22 | 23 | |
23 | 24 | exports.create = function (api) { |
25 | + const i18n = api.intl.sync.i18n | |
24 | 26 | return nest('message.html.compose', function ({shrink = true, meta, prepublish, placeholder = 'Write a message'}, cb) { |
25 | 27 | var files = [] |
26 | 28 | var filesById = {} |
27 | 29 | var focused = Value(false) |
@@ -95,9 +97,9 @@ | ||
95 | 97 | |
96 | 98 | var publishBtn = h('button', { |
97 | 99 | 'ev-click': publish, |
98 | 100 | disabled: publishing |
99 | - }, when(publishing, 'Publishing...', 'Publish')) | |
101 | + }, when(publishing, i18n('Publishing...'), i18n('Publish'))) | |
100 | 102 | |
101 | 103 | var actions = h('section.actions', [ |
102 | 104 | fileInput, |
103 | 105 | publishBtn |
modules/message/sheet/likes.js | ||
---|---|---|
@@ -8,22 +8,24 @@ | ||
8 | 8 | 'contact.obs.following': 'first', |
9 | 9 | 'profile.obs.rank': 'first', |
10 | 10 | 'about.html.image': 'first', |
11 | 11 | 'about.obs.name': 'first', |
12 | - 'app.navigate': 'first' | |
12 | + 'app.navigate': 'first', | |
13 | + 'intl.sync.i18n': 'first', | |
13 | 14 | }) |
14 | 15 | |
15 | 16 | exports.gives = nest('message.sheet.likes') |
16 | 17 | |
17 | 18 | exports.create = function (api) { |
19 | + const i18n = api.intl.sync.i18n | |
18 | 20 | return nest('message.sheet.likes', function (ids) { |
19 | 21 | api.sheet.display(close => { |
20 | 22 | var content = h('div', { |
21 | 23 | style: { padding: '20px' } |
22 | 24 | }, [ |
23 | 25 | h('h2', { |
24 | 26 | style: { 'font-weight': 'normal' } |
25 | - }, ['Liked by']), | |
27 | + }, [i18n('Liked by')]), | |
26 | 28 | renderContactBlock(ids) |
27 | 29 | ]) |
28 | 30 | |
29 | 31 | catchLinks(content, (href, external) => { |
@@ -37,9 +39,9 @@ | ||
37 | 39 | content, |
38 | 40 | footer: [ |
39 | 41 | h('button -close', { |
40 | 42 | 'ev-click': close |
41 | - }, 'Close') | |
43 | + }, i18n('Close')) | |
42 | 44 | ] |
43 | 45 | } |
44 | 46 | }) |
45 | 47 | }) |
modules/page/html/render/all.js | ||
---|---|---|
@@ -4,29 +4,31 @@ | ||
4 | 4 | exports.needs = nest({ |
5 | 5 | 'feed.pull.public': 'first', |
6 | 6 | 'message.html.compose': 'first', |
7 | 7 | 'message.async.publish': 'first', |
8 | - 'feed.html.rollup': 'first' | |
8 | + 'feed.html.rollup': 'first', | |
9 | + 'intl.sync.i18n': 'first', | |
9 | 10 | }) |
10 | 11 | |
11 | 12 | exports.gives = nest({ |
12 | 13 | 'page.html.render': true |
13 | 14 | }) |
14 | 15 | |
15 | 16 | exports.create = function (api) { |
17 | + const i18n = api.intl.sync.i18n | |
16 | 18 | return nest('page.html.render', page) |
17 | 19 | |
18 | 20 | function page (path) { |
19 | 21 | if (path !== '/all') return // "/" is a sigil for "page" |
20 | 22 | |
21 | 23 | var prepend = [ |
22 | 24 | h('PageHeading', [ |
23 | 25 | h('h1', [ |
24 | - 'All Posts from Your ', | |
25 | - h('strong', 'Extended Network') | |
26 | + i18n('All Posts from Your '), | |
27 | + h('strong', i18n('Extended Network')) | |
26 | 28 | ]) |
27 | 29 | ]), |
28 | - api.message.html.compose({ meta: { type: 'post' }, placeholder: 'Write a public message' }) | |
30 | + api.message.html.compose({ meta: { type: 'post' }, placeholder: i18n('Write a public message') }) | |
29 | 31 | ] |
30 | 32 | |
31 | 33 | var feedView = api.feed.html.rollup(api.feed.pull.public, { |
32 | 34 | bumpFilter: (msg) => { |
modules/page/html/render/channel.js | ||
---|---|---|
@@ -7,14 +7,16 @@ | ||
7 | 7 | 'feed.html.rollup': 'first', |
8 | 8 | 'feed.pull.channel': 'first', |
9 | 9 | 'sbot.pull.log': 'first', |
10 | 10 | 'message.async.publish': 'first', |
11 | - 'keys.sync.id': 'first' | |
11 | + 'keys.sync.id': 'first', | |
12 | + 'intl.sync.i18n': 'first', | |
12 | 13 | }) |
13 | 14 | |
14 | 15 | exports.gives = nest('page.html.render') |
15 | 16 | |
16 | 17 | exports.create = function (api) { |
18 | + const i18n = api.intl.sync.i18n | |
17 | 19 | return nest('page.html.render', function channel (path) { |
18 | 20 | if (path[0] !== '#') return |
19 | 21 | |
20 | 22 | var channel = path.substr(1) |
@@ -26,21 +28,21 @@ | ||
26 | 28 | h('div.meta', [ |
27 | 29 | when(subscribedChannels.has(channel), |
28 | 30 | h('a.ToggleButton.-unsubscribe', { |
29 | 31 | 'href': '#', |
30 | - 'title': 'Click to unsubscribe', | |
32 | + 'title': i18n('Click to unsubscribe'), | |
31 | 33 | 'ev-click': send(unsubscribe, channel) |
32 | - }, 'Subscribed'), | |
34 | + }, i18n('Subscribed')), | |
33 | 35 | h('a.ToggleButton.-subscribe', { |
34 | 36 | 'href': '#', |
35 | 37 | 'ev-click': send(subscribe, channel) |
36 | - }, 'Subscribe') | |
38 | + }, i18n('Subscribe')) | |
37 | 39 | ) |
38 | 40 | ]) |
39 | 41 | ]), |
40 | 42 | api.message.html.compose({ |
41 | 43 | meta: {type: 'post', channel}, |
42 | - placeholder: 'Write a message in this channel\n\n\n\nPeople who follow you or subscribe to this channel will also see this message in their main feed.\n\nTo create a new channel, type the channel name (preceded by a #) into the search box above. e.g #cat-pics' | |
44 | + placeholder: i18n('Write a message in this channel') | |
43 | 45 | }) |
44 | 46 | ] |
45 | 47 | |
46 | 48 | return api.feed.html.rollup(api.feed.pull.channel(channel), { |
modules/page/html/render/channels.js | ||
---|---|---|
@@ -6,14 +6,16 @@ | ||
6 | 6 | 'keys.sync.id': 'first', |
7 | 7 | 'channel.obs': { |
8 | 8 | subscribed: 'first', |
9 | 9 | recent: 'first' |
10 | - } | |
10 | + }, | |
11 | + 'intl.sync.i18n': 'first' | |
11 | 12 | }) |
12 | 13 | |
13 | 14 | exports.gives = nest('page.html.render') |
14 | 15 | |
15 | 16 | exports.create = function(api){ |
17 | + const i18n = api.intl.sync.i18n | |
16 | 18 | return nest('page.html.render', function page(path){ |
17 | 19 | if (path !== '/channels') return |
18 | 20 | |
19 | 21 | var id = api.keys.sync.id() |
@@ -38,12 +40,12 @@ | ||
38 | 40 | h('span.name', '#' + channel), |
39 | 41 | when(subscribed, |
40 | 42 | h('a.-unsubscribe', { |
41 | 43 | 'ev-click': send(unsubscribe, channel) |
42 | - }, 'Unsubscribe'), | |
44 | + }, i18n('Unsubscribe')), | |
43 | 45 | h('a.-subscribe', { |
44 | 46 | 'ev-click': send(subscribe, channel) |
45 | - }, 'Subscribe') | |
47 | + }, i18n('Subscribe')) | |
46 | 48 | ) |
47 | 49 | ]) |
48 | 50 | }, {maxTime: 5, idle: true}) |
49 | 51 | ]) |
modules/page/html/render/gatherings.js | ||
---|---|---|
@@ -7,27 +7,29 @@ | ||
7 | 7 | 'feed.pull.public': 'first', |
8 | 8 | 'gathering.sheet.edit': 'first', |
9 | 9 | 'keys.sync.id': 'first', |
10 | 10 | 'contact.obs.following': 'first', |
11 | - 'sbot.pull.stream': 'first' | |
11 | + 'sbot.pull.stream': 'first', | |
12 | + 'intl.sync.i18n': 'first', | |
12 | 13 | }) |
13 | 14 | |
14 | 15 | exports.gives = nest('page.html.render') |
15 | 16 | |
16 | 17 | exports.create = function (api) { |
18 | + const i18n = api.intl.sync.i18n | |
17 | 19 | return nest('page.html.render', function channel (path) { |
18 | 20 | if (path !== '/gatherings') return |
19 | 21 | |
20 | 22 | var id = api.keys.sync.id() |
21 | 23 | var following = api.contact.obs.following(id) |
22 | 24 | |
23 | 25 | var prepend = [ |
24 | 26 | h('PageHeading', [ |
25 | - h('h1', [h('strong', 'Gatherings'), ' from your extended network']), | |
27 | + h('h1', [h('strong', i18n('Gatherings')), i18n(' from your extended network')]), | |
26 | 28 | h('div.meta', [ |
27 | 29 | h('button -add', { |
28 | 30 | 'ev-click': createGathering |
29 | - }, '+ Add Gathering') | |
31 | + }, i18n('+ Add Gathering')) | |
30 | 32 | ]) |
31 | 33 | ]) |
32 | 34 | ] |
33 | 35 |
modules/page/html/render/message.js | ||
---|---|---|
@@ -9,14 +9,16 @@ | ||
9 | 9 | 'message.html': { |
10 | 10 | render: 'first', |
11 | 11 | compose: 'first' |
12 | 12 | }, |
13 | - 'sbot.async.get': 'first' | |
13 | + 'sbot.async.get': 'first', | |
14 | + 'intl.sync.i18n':'first', | |
14 | 15 | }) |
15 | 16 | |
16 | 17 | exports.gives = nest('page.html.render') |
17 | 18 | |
18 | 19 | exports.create = function (api) { |
20 | + const i18n = api.intl.sync.i18n | |
19 | 21 | return nest('page.html.render', function (id) { |
20 | 22 | if (!ref.isMsg(id)) return |
21 | 23 | var loader = h('div', {className: 'Loading -large'}) |
22 | 24 | |
@@ -32,15 +34,15 @@ | ||
32 | 34 | |
33 | 35 | var compose = api.message.html.compose({ |
34 | 36 | meta, |
35 | 37 | shrink: false, |
36 | - placeholder: when(meta.recps, 'Write a private reply', 'Write a public reply') | |
38 | + placeholder: when(meta.recps, i18n('Write a private reply'), i18n('Write a public reply')) | |
37 | 39 | }) |
38 | 40 | |
39 | 41 | api.sbot.async.get(id, (err, value) => { |
40 | 42 | if (err) { |
41 | 43 | return result.set(h('PageHeading', [ |
42 | - h('h1', 'Cannot load thread. Root message missing.') | |
44 | + h('h1', i18n('Cannot load thead')) | |
43 | 45 | ])) |
44 | 46 | } |
45 | 47 | |
46 | 48 | if (typeof value.content === 'string') { |
@@ -48,9 +50,9 @@ | ||
48 | 50 | } |
49 | 51 | |
50 | 52 | if (!value) { |
51 | 53 | return result.set(h('PageHeading', [ |
52 | - h('h1', 'Cannot display message.') | |
54 | + h('h1', i18n('Cannot display message.')) | |
53 | 55 | ])) |
54 | 56 | } |
55 | 57 | |
56 | 58 | // what happens in private stays in private! |
@@ -66,9 +68,9 @@ | ||
66 | 68 | meta.branch.set(isReply ? thread.branchId : thread.lastId) |
67 | 69 | |
68 | 70 | var container = h('Thread', [ |
69 | 71 | h('div.messages', [ |
70 | - when(thread.branchId, h('a.full', {href: thread.rootId}, ['View full thread'])), | |
72 | + when(thread.branchId, h('a.full', {href: thread.rootId}, [i18n('View full thread')])), | |
71 | 73 | map(thread.messages, (msg) => { |
72 | 74 | return computed([msg, thread.previousKey(msg)], (msg, previousId) => { |
73 | 75 | return api.message.html.render(msg, {pageId: id, previousId, includeReferences: true}) |
74 | 76 | }) |
modules/page/html/render/private.js | ||
---|---|---|
@@ -4,14 +4,16 @@ | ||
4 | 4 | exports.needs = nest({ |
5 | 5 | 'feed.html.rollup': 'first', |
6 | 6 | 'feed.pull.private': 'first', |
7 | 7 | 'message.html.compose': 'first', |
8 | - 'keys.sync.id': 'first' | |
8 | + 'keys.sync.id': 'first', | |
9 | + 'intl.sync.i18n': 'first', | |
9 | 10 | }) |
10 | 11 | |
11 | 12 | exports.gives = nest('page.html.render') |
12 | 13 | |
13 | 14 | exports.create = function (api) { |
15 | + const i18n = api.intl.sync.i18n | |
14 | 16 | return nest('page.html.render', function channel (path) { |
15 | 17 | if (path !== '/private') return |
16 | 18 | |
17 | 19 | var id = api.keys.sync.id() |
@@ -23,9 +25,9 @@ | ||
23 | 25 | return ref.isFeed(typeof e === 'string' ? e : e.link) |
24 | 26 | }) |
25 | 27 | return msg |
26 | 28 | }, |
27 | - placeholder: `Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.` | |
29 | + placeholder: i18n('Write a private message') | |
28 | 30 | }) |
29 | 31 | ] |
30 | 32 | |
31 | 33 | return api.feed.html.rollup(api.feed.pull.private, { prepend }) |
modules/page/html/render/profile.js | ||
---|---|---|
@@ -25,13 +25,15 @@ | ||
25 | 25 | 'profile.sheet.edit': 'first', |
26 | 26 | 'contact.obs': { |
27 | 27 | followers: 'first', |
28 | 28 | following: 'first' |
29 | - } | |
29 | + }, | |
30 | + 'intl.sync.i18n': 'first', | |
30 | 31 | }) |
31 | 32 | exports.gives = nest('page.html.render') |
32 | 33 | |
33 | 34 | exports.create = function (api) { |
35 | + const i18n = api.intl.sync.i18n | |
34 | 36 | return nest('page.html.render', function profile (id) { |
35 | 37 | if (!ref.isFeed(id)) return |
36 | 38 | |
37 | 39 | var name = api.about.obs.name(id) |
@@ -83,9 +85,9 @@ | ||
83 | 85 | classList: [ |
84 | 86 | when(isSelf, '-self'), |
85 | 87 | when(isAssigned, '-assigned') |
86 | 88 | ], |
87 | - title: nameList(when(isSelf, 'Self Assigned', 'Assigned By'), item.value) | |
89 | + title: nameList(when(isSelf, i18n('Self Assigned'), i18n('Assigned By')), item.value) | |
88 | 90 | }, [ |
89 | 91 | item.key |
90 | 92 | ]) |
91 | 93 | }), |
@@ -111,9 +113,9 @@ | ||
111 | 113 | classList: [ |
112 | 114 | when(isSelf, '-self'), |
113 | 115 | when(isAssigned, '-assigned') |
114 | 116 | ], |
115 | - title: nameList(when(isSelf, 'Self Assigned', 'Assigned By'), item.value) | |
117 | + title: nameList(when(isSelf, i18n('Self Assigned'), i18n('Assigned By')), item.value) | |
116 | 118 | }, [ |
117 | 119 | h('img', { |
118 | 120 | className: 'Avatar', |
119 | 121 | style: { 'background-color': api.about.obs.color(id) }, |
@@ -137,20 +139,20 @@ | ||
137 | 139 | h('div.title', [ |
138 | 140 | h('h1', [name]), |
139 | 141 | h('div.meta', [ |
140 | 142 | when(id === yourId, [ |
141 | - h('button', {'ev-click': api.profile.sheet.edit}, 'Edit Your Profile') | |
143 | + h('button', {'ev-click': api.profile.sheet.edit}, i18n('Edit Your Profile')) | |
142 | 144 | ], [ |
143 | 145 | when(youFollow, |
144 | 146 | h('a.ToggleButton.-unsubscribe', { |
145 | 147 | 'href': '#', |
146 | - 'title': 'Click to unfollow', | |
148 | + 'title': i18n('Click to unfollow'), | |
147 | 149 | 'ev-click': send(unfollow, id) |
148 | - }, when(isFriends, 'Friends', 'Following')), | |
150 | + }, when(isFriends, i18n('Friends'), i18n('Following'))), | |
149 | 151 | h('a.ToggleButton.-subscribe', { |
150 | 152 | 'href': '#', |
151 | 153 | 'ev-click': send(follow, id) |
152 | - }, when(followsYou, 'Follow Back', 'Follow')) | |
154 | + }, when(followsYou, i18n('Follow Back'), i18n('Follow'))) | |
153 | 155 | ) |
154 | 156 | ]) |
155 | 157 | ]) |
156 | 158 | ]), |
@@ -177,11 +179,11 @@ | ||
177 | 179 | ]), |
178 | 180 | h('div.side.-right', [ |
179 | 181 | when(friendsLoaded, |
180 | 182 | h('div', [ |
181 | - renderContactBlock('Friends', friends, yourFollows), | |
182 | - renderContactBlock('Followers', followers, yourFollows), | |
183 | - renderContactBlock('Following', following, yourFollows) | |
183 | + renderContactBlock(i18n('Friends'), friends, yourFollows), | |
184 | + renderContactBlock(i18n('Followers'), followers, yourFollows), | |
185 | + renderContactBlock(i18n('Following'), following, yourFollows) | |
184 | 186 | ]), |
185 | 187 | h('div', {className: 'Loading'}) |
186 | 188 | ) |
187 | 189 | ]) |
@@ -273,9 +275,9 @@ | ||
273 | 275 | h('h2', { |
274 | 276 | style: { |
275 | 277 | 'font-weight': 'normal' |
276 | 278 | } |
277 | - }, ['What whould you like to call ', h('strong', [currentName]), '?']), | |
279 | + }, [i18n('What whould you like to call '), h('strong', [currentName]), '?']), | |
278 | 280 | input |
279 | 281 | ]), |
280 | 282 | footer: [ |
281 | 283 | h('button -save', { |
@@ -289,12 +291,12 @@ | ||
289 | 291 | }) |
290 | 292 | } |
291 | 293 | close() |
292 | 294 | } |
293 | - }, 'Confirm'), | |
295 | + }, i18n('Confirm')), | |
294 | 296 | h('button -cancel', { |
295 | 297 | 'ev-click': close |
296 | - }, 'Cancel') | |
298 | + }, i18n('Cancel')) | |
297 | 299 | ] |
298 | 300 | } |
299 | 301 | }) |
300 | 302 | } |
modules/page/html/render/public.js | ||
---|---|---|
@@ -28,16 +28,18 @@ | ||
28 | 28 | subscribed: 'first', |
29 | 29 | recent: 'first' |
30 | 30 | }, |
31 | 31 | 'keys.sync.id': 'first', |
32 | - 'settings.obs.get': 'first' | |
32 | + 'settings.obs.get': 'first', | |
33 | + 'intl.sync.i18n': 'first', | |
33 | 34 | }) |
34 | 35 | |
35 | 36 | exports.gives = nest({ |
36 | 37 | 'page.html.render': true |
37 | 38 | }) |
38 | 39 | |
39 | 40 | exports.create = function (api) { |
41 | + const i18n = api.intl.sync.i18n | |
40 | 42 | return nest('page.html.render', page) |
41 | 43 | |
42 | 44 | function page (path) { |
43 | 45 | if (path !== '/public') return // "/" is a sigil for "page" |
@@ -52,9 +54,9 @@ | ||
52 | 54 | var localPeers = api.sbot.obs.localPeers() |
53 | 55 | var connectedPubs = computed([connectedPeers, localPeers], (c, l) => c.filter(x => !l.includes(x))) |
54 | 56 | |
55 | 57 | var prepend = [ |
56 | - api.message.html.compose({ meta: { type: 'post' }, placeholder: 'Write a public message' }) | |
58 | + api.message.html.compose({ meta: { type: 'post' }, placeholder: i18n('Write a public message') }) | |
57 | 59 | ] |
58 | 60 | |
59 | 61 | var getStream = (opts) => { |
60 | 62 | if (opts.lt != null && !opts.lt.marker) { |
@@ -123,11 +125,11 @@ | ||
123 | 125 | }) |
124 | 126 | return [ |
125 | 127 | h('button -pub -full', { |
126 | 128 | 'ev-click': api.invite.sheet |
127 | - }, '+ Join Pub'), | |
128 | - when(loading, [ h('Loading') ], [ | |
129 | - when(computed(channels, x => x.length), h('h2', 'Active Channels')), | |
129 | + }, i18n('+ Join Pub')), | |
130 | + when(loading, [ h("Loading") ], [ | |
131 | + when(computed(channels, x => x.length), h('h2', i18n("Active Channels"))), | |
130 | 132 | h('div', { |
131 | 133 | classList: 'ChannelList', |
132 | 134 | hidden: loading |
133 | 135 | }, [ |
@@ -142,23 +144,23 @@ | ||
142 | 144 | h('span.name', '#' + channel), |
143 | 145 | when(subscribed, |
144 | 146 | h('a.-unsubscribe', { |
145 | 147 | 'ev-click': send(unsubscribe, channel) |
146 | - }, 'Unsubscribe'), | |
148 | + }, i18n('Unsubscribe')), | |
147 | 149 | h('a.-subscribe', { |
148 | 150 | 'ev-click': send(subscribe, channel) |
149 | - }, 'Subscribe') | |
151 | + }, i18n('Subscribe')) | |
150 | 152 | ) |
151 | 153 | ]) |
152 | 154 | }, {maxTime: 5}), |
153 | - h('a.channel -more', {href: '/channels'}, 'More Channels...') | |
155 | + h('a.channel -more', {href: '/channels'}, i18n('More Channels...')) | |
154 | 156 | ]) |
155 | 157 | ]), |
156 | 158 | |
157 | - PeerList(localPeers, 'Local'), | |
158 | - PeerList(connectedPubs, 'Connected Pubs'), | |
159 | + PeerList(localPeers, i18n('Local')), | |
160 | + PeerList(connectedPubs, i18n('Connected Pubs')), | |
159 | 161 | |
160 | - when(computed(whoToFollow, x => x.length), h('h2', 'Who to follow')), | |
162 | + when(computed(whoToFollow, x => x.length), h('h2', i18n('Who to follow'))), | |
161 | 163 | when(following.sync, |
162 | 164 | h('div', { |
163 | 165 | classList: 'ProfileList' |
164 | 166 | }, [ |
modules/page/html/render/search.js | ||
---|---|---|
@@ -10,14 +10,16 @@ | ||
10 | 10 | |
11 | 11 | exports.needs = nest({ |
12 | 12 | 'sbot.pull.stream': 'first', |
13 | 13 | 'keys.sync.id': 'first', |
14 | - 'message.html.render': 'first' | |
14 | + 'message.html.render': 'first', | |
15 | + 'intl.sync.i18n': 'first' | |
15 | 16 | }) |
16 | 17 | |
17 | 18 | exports.gives = nest('page.html.render') |
18 | 19 | |
19 | 20 | exports.create = function (api) { |
21 | + const i18n = api.intl.sync.i18n | |
20 | 22 | return nest('page.html.render', function channel (path) { |
21 | 23 | if (path[0] !== '?') return |
22 | 24 | |
23 | 25 | var queryStr = path.substr(1).trim() |
@@ -28,13 +30,13 @@ | ||
28 | 30 | var updates = Value(0) |
29 | 31 | var aborter = null |
30 | 32 | |
31 | 33 | const searchHeader = h('div', {className: 'PageHeading'}, [ |
32 | - h('h1', [h('strong', 'Search Results:'), ' ', query.join(' ')]) | |
34 | + h('h1', [h('strong', i18n('Search Results:')), ' ', query.join(' ')]) | |
33 | 35 | ]) |
34 | 36 | |
35 | 37 | var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [ |
36 | - 'Show ', h('strong', [updates]), ' ', plural(updates, 'update', 'updates') | |
38 | + 'Show ', h('strong', [updates]), ' ', plural(updates, i18n('update'), i18n('updates')) | |
37 | 39 | ]) |
38 | 40 | |
39 | 41 | var content = Proxy() |
40 | 42 | var container = h('Scroller', { |
@@ -48,9 +50,9 @@ | ||
48 | 50 | style: { |
49 | 51 | 'padding': '60px 0', |
50 | 52 | 'font-size': '150%' |
51 | 53 | } |
52 | - }, [h('strong', 'Search completed.'), ' ', count, ' ', plural(count, 'result', 'results'), ' found'])) | |
54 | + }, [h('strong', i18n('Search completed.')), ' ', count, ' ', plural(count, i18n('result found'), i18n('results found'))])) | |
53 | 55 | ]) |
54 | 56 | ]) |
55 | 57 | ]) |
56 | 58 |
modules/page/html/render/settings.js | ||
---|---|---|
@@ -1,59 +1,87 @@ | ||
1 | 1 | var { h, computed } = require('mutant') |
2 | 2 | var nest = require('depnest') |
3 | +var appRoot = require('app-root-path') | |
3 | 4 | |
4 | 5 | var themeNames = Object.keys(require('../../../../styles')) |
5 | 6 | |
6 | 7 | exports.needs = nest({ |
7 | 8 | 'settings.obs.get': 'first', |
8 | 9 | 'settings.sync.set': 'first', |
10 | + 'intl.sync.locales': 'first', | |
11 | + 'intl.sync.i18n': 'first' | |
9 | 12 | }) |
10 | 13 | |
11 | 14 | exports.gives = nest('page.html.render') |
12 | 15 | |
13 | 16 | exports.create = function (api) { |
14 | 17 | return nest('page.html.render', function channel (path) { |
15 | 18 | if (path !== '/settings') return |
16 | - | |
19 | + const i18n = api.intl.sync.i18n | |
20 | + | |
17 | 21 | const currentTheme = api.settings.obs.get('patchwork.theme') |
22 | + const currentLang = api.settings.obs.get('patchwork.lang') | |
23 | + const langNames = api.intl.sync.locales() | |
18 | 24 | const filterFollowing = api.settings.obs.get('filters.following') |
19 | 25 | |
20 | 26 | var prepend = [ |
21 | 27 | h('PageHeading', [ |
22 | 28 | h('h1', [ |
23 | - h('strong', 'Settings') | |
24 | - ]) | |
29 | + h('strong', i18n('Settings')) | |
30 | + ]), | |
25 | 31 | ]) |
26 | 32 | ] |
27 | 33 | |
28 | 34 | return h('Scroller', { style: { overflow: 'auto' } }, [ |
29 | 35 | h('div.wrapper', [ |
30 | 36 | h('section.prepend', prepend), |
31 | 37 | h('section.content', [ |
32 | 38 | h('section', [ |
33 | - h('h2', 'Theme'), | |
34 | - h('select', { | |
35 | - style: { | |
36 | - 'font-size': '120%' | |
37 | - }, | |
38 | - value: currentTheme, | |
39 | - 'ev-change': (ev) => api.settings.sync.set({ | |
40 | - patchwork: {theme: ev.target.value} | |
39 | + h('h2', i18n('Theme')), | |
40 | + computed(currentTheme, currentTheme => { | |
41 | + return themeNames.map(name => { | |
42 | + const style = currentTheme == name | |
43 | + ? { 'margin-right': '1rem', 'border-color': 'teal' } | |
44 | + : { 'margin-right': '1rem' } | |
45 | + | |
46 | + return h('button', { | |
47 | + 'ev-click': () => api.settings.sync.set({ | |
48 | + patchwork: {theme: name} | |
49 | + }), | |
50 | + style | |
51 | + }, name) | |
41 | 52 | }) |
42 | 53 | }, [ |
43 | 54 | themeNames.map(name => h('option', {value: name}, [name])) |
44 | 55 | ]) |
45 | 56 | ]), |
46 | 57 | h('section', [ |
47 | - h('h2', 'Filters'), | |
58 | + h('h2', i18n('Language')), | |
59 | + computed(currentLang, currentLang => { | |
60 | + return langNames.map(lang => { | |
61 | + const style = currentLang == lang | |
62 | + ? { 'margin-right': '1rem', 'border-color': 'teal' } | |
63 | + : { 'margin-right': '1rem' } | |
64 | + | |
65 | + return h('button', { | |
66 | + 'ev-click': () => api.settings.sync.set({ | |
67 | + patchwork: {lang: lang} | |
68 | + }), | |
69 | + style | |
70 | + }, lang) | |
71 | + }) | |
72 | + }) | |
73 | + ]), | |
74 | + h('section', [ | |
75 | + h('h2', i18n('Filters')), | |
48 | 76 | h('label', [ |
49 | 77 | h('input', { |
50 | 78 | type: 'checkbox', |
51 | 79 | checked: filterFollowing, |
52 | 80 | 'ev-change': (ev) => api.settings.sync.set({ |
53 | 81 | filters: {following: ev.target.checked} |
54 | 82 | }) |
55 | - }), ' Hide following messages' | |
83 | + }), i18n(' Hide following messages') | |
56 | 84 | ]) |
57 | 85 | ]) |
58 | 86 | ]) |
59 | 87 | ]) |
modules/profile/sheet/edit.js | ||
---|---|---|
@@ -15,12 +15,14 @@ | ||
15 | 15 | image: 'first', |
16 | 16 | color: 'first' |
17 | 17 | }, |
18 | 18 | 'blob.html.input': 'first', |
19 | - 'blob.sync.url': 'first' | |
19 | + 'blob.sync.url': 'first', | |
20 | + 'intl.sync.i18n': 'first', | |
20 | 21 | }) |
21 | 22 | |
22 | 23 | exports.create = function (api) { |
24 | + const i18n = api.intl.sync.i18n | |
23 | 25 | return nest('profile.sheet.edit', function () { |
24 | 26 | var id = api.keys.sync.id() |
25 | 27 | api.sheet.display(close => { |
26 | 28 | var currentName = api.about.obs.name(id) |
@@ -44,17 +46,17 @@ | ||
44 | 46 | h('h2', { |
45 | 47 | style: { |
46 | 48 | 'font-weight': 'normal' |
47 | 49 | } |
48 | - }, ['Your Profile']), | |
50 | + }, [i18n('Your Profile')]), | |
49 | 51 | h('ProfileEditor', [ |
50 | 52 | h('div.side', [ |
51 | 53 | h('ImageInput', [ |
52 | 54 | h('img', { |
53 | 55 | style: { 'background-color': api.about.obs.color(id) }, |
54 | 56 | src: computed(chosenImage, (id) => id ? api.blob.sync.url(id) : fallbackImageUrl) |
55 | 57 | }), |
56 | - h('span', ['🖼 Choose Profile Image...']), | |
58 | + h('span', ['🖼 ', i18n('Choose Profile Image...')]), | |
57 | 59 | api.blob.html.input(file => { |
58 | 60 | chosenImage.set(file.link) |
59 | 61 | }, { |
60 | 62 | accept: 'image/*', |
@@ -63,13 +65,13 @@ | ||
63 | 65 | ]) |
64 | 66 | ]), |
65 | 67 | h('div.main', [ |
66 | 68 | h('input.name', { |
67 | - placeholder: 'Choose a name', | |
69 | + placeholder: i18n('Choose a name'), | |
68 | 70 | hooks: [ValueHook(chosenName), FocusHook()] |
69 | 71 | }), |
70 | 72 | h('textarea.description', { |
71 | - placeholder: 'Describe yourself (if you want)', | |
73 | + placeholder: i18n('Describe yourself (if you want)'), | |
72 | 74 | hooks: [ValueHook(chosenDescription)] |
73 | 75 | }) |
74 | 76 | ]) |
75 | 77 | ]) |
@@ -77,12 +79,12 @@ | ||
77 | 79 | footer: [ |
78 | 80 | h('button -save', { |
79 | 81 | 'ev-click': save, |
80 | 82 | 'disabled': publishing |
81 | - }, when(publishing, 'Publishing...', 'Publish')), | |
83 | + }, when(publishing, i18n('Publishing...'), i18n('Publish'))), | |
82 | 84 | h('button -cancel', { |
83 | 85 | 'ev-click': close |
84 | - }, 'Cancel') | |
86 | + }, i18n('Cancel')) | |
85 | 87 | ] |
86 | 88 | } |
87 | 89 | |
88 | 90 | function save () { |
@@ -103,11 +105,11 @@ | ||
103 | 105 | if (err) { |
104 | 106 | publishing.set(false) |
105 | 107 | showDialog({ |
106 | 108 | type: 'error', |
107 | - title: 'Error', | |
108 | - buttons: ['OK'], | |
109 | - message: 'An error occurred while attempting to publish about message.', | |
109 | + title: i18n('Error'), | |
110 | + buttons: [i18n('OK')], | |
111 | + message: i18n('An error occurred while attempting to publish about message.'), | |
110 | 112 | detail: err.message |
111 | 113 | }) |
112 | 114 | } else { |
113 | 115 | close() |
overrides/patchcore/lib/timeAgo.js | ||
---|---|---|
@@ -1,0 +1,42 @@ | ||
1 | +const Value = require('mutant/value') | |
2 | +const computed = require('mutant/computed') | |
3 | +const nest = require('depnest') | |
4 | +const human = require('human-time') | |
5 | + | |
6 | +exports.gives = nest('lib.obs.timeAgo') | |
7 | + | |
8 | +exports.needs = nest({ | |
9 | + 'intl.sync.time': 'first' | |
10 | +}) | |
11 | + | |
12 | +exports.create = function (api) { | |
13 | + return nest('lib.obs.timeAgo', timeAgo) | |
14 | + | |
15 | + function timeAgo (timestamp) { | |
16 | + var timer | |
17 | + var value = Value(TimeIntl(timestamp)) | |
18 | + return computed([value], (a) => a, { | |
19 | + onListen: () => { | |
20 | + timer = setInterval(refresh, 30e3) | |
21 | + refresh() | |
22 | + }, | |
23 | + onUnlisten: () => { | |
24 | + clearInterval(timer) | |
25 | + } | |
26 | + }, { | |
27 | + idle: true | |
28 | + }) | |
29 | + | |
30 | + function refresh () { | |
31 | + value.set(TimeIntl(timestamp)) | |
32 | + } | |
33 | + | |
34 | + function TimeIntl(timestamp) { | |
35 | + return api.intl.sync.time(Time(timestamp)) | |
36 | + } | |
37 | + } | |
38 | +} | |
39 | + | |
40 | +function Time (timestamp) { | |
41 | + return human(new Date(timestamp)) | |
42 | +} |
package.json | ||
---|---|---|
@@ -13,8 +13,9 @@ | ||
13 | 13 | }, |
14 | 14 | "author": "Secure Scuttlebutt Consortium", |
15 | 15 | "license": "AGPL-3.0", |
16 | 16 | "dependencies": { |
17 | + "app-root-path": "^2.0.1", | |
17 | 18 | "bulk-require": "^1.0.0", |
18 | 19 | "compare-version": "^0.1.2", |
19 | 20 | "cross-script": "^1.0.1", |
20 | 21 | "deep-equal": "^1.0.1", |
@@ -26,8 +27,9 @@ | ||
26 | 27 | "fix-path": "^2.1.0", |
27 | 28 | "flatpickr": "^3.0.5-1", |
28 | 29 | "flumeview-level": "^2.0.3", |
29 | 30 | "hashlru": "^2.2.0", |
31 | + "i18n": "^0.8.3", | |
30 | 32 | "insert-css": "~2.0.0", |
31 | 33 | "level": "~1.7.0", |
32 | 34 | "lrucache": "^1.0.2", |
33 | 35 | "micro-css": "^2.0.1", |
plugs/message/html/layout/default.js | ||
---|---|---|
@@ -13,14 +13,16 @@ | ||
13 | 13 | action: 'map', |
14 | 14 | timestamp: 'first', |
15 | 15 | backlinks: 'first' |
16 | 16 | }, |
17 | - 'about.html.image': 'first' | |
17 | + 'about.html.image': 'first', | |
18 | + 'intl.sync.i18n': 'first', | |
18 | 19 | }) |
19 | 20 | |
20 | 21 | exports.gives = nest('message.html.layout') |
21 | 22 | |
22 | 23 | exports.create = function (api) { |
24 | + const i18n = api.intl.sync.i18n | |
23 | 25 | return nest('message.html.layout', layout) |
24 | 26 | |
25 | 27 | function layout (msg, {layout, previousId, priority, content, includeReferences = false}) { |
26 | 28 | if (!(layout === undefined || layout === 'default')) return |
@@ -32,13 +34,13 @@ | ||
32 | 34 | classList.push('-reply') |
33 | 35 | var branch = msg.value.content.branch |
34 | 36 | if (branch) { |
35 | 37 | if (!previousId || (previousId && last(branch) && previousId !== last(branch))) { |
36 | - replyInfo = h('span', ['in reply to ', api.message.html.link(last(branch))]) | |
38 | + replyInfo = h('span', [i18n('in reply to '), api.message.html.link(last(branch))]) | |
37 | 39 | } |
38 | 40 | } |
39 | 41 | } else if (msg.value.content.project) { |
40 | - replyInfo = h('span', ['on ', api.message.html.link(msg.value.content.project)]) | |
42 | + replyInfo = h('span', [i18n('on '), api.message.html.link(msg.value.content.project)]) | |
41 | 43 | } |
42 | 44 | |
43 | 45 | if (priority === 2) { |
44 | 46 | classList.push('-new') |
@@ -65,9 +67,9 @@ | ||
65 | 67 | |
66 | 68 | function messageHeader (msg, {replyInfo, priority}) { |
67 | 69 | var additionalMeta = [] |
68 | 70 | if (priority >= 2) { |
69 | - additionalMeta.push(h('span.flag -new', {title: 'New Message'})) | |
71 | + additionalMeta.push(h('span.flag -new', {title: i18n('New Message')})) | |
70 | 72 | } |
71 | 73 | return h('header', [ |
72 | 74 | h('div.main', [ |
73 | 75 | h('a.avatar', {href: `${msg.value.author}`}, [ |
plugs/message/html/meta/likes.js | ||
---|---|---|
@@ -1,14 +1,17 @@ | ||
1 | 1 | var nest = require('depnest') |
2 | 2 | var { h, computed, map, send } = require('mutant') |
3 | + | |
3 | 4 | exports.gives = nest('message.html.meta') |
4 | 5 | exports.needs = nest({ |
5 | 6 | 'message.obs.likes': 'first', |
6 | 7 | 'message.sheet.likes': 'first', |
7 | - 'about.obs.name': 'first' | |
8 | + 'about.obs.name': 'first', | |
9 | + 'intl.sync.i18n': 'first', | |
8 | 10 | }) |
9 | 11 | |
10 | 12 | exports.create = function (api) { |
13 | + const i18n = api.intl.sync.i18n | |
11 | 14 | return nest('message.html.meta', function likes (msg) { |
12 | 15 | if (msg.key) { |
13 | 16 | return computed(api.message.obs.likes(msg.key), likeCount) |
14 | 17 | } |
@@ -19,15 +22,15 @@ | ||
19 | 22 | return [' ', h('a.likes', { |
20 | 23 | title: names(likes), |
21 | 24 | href: '#', |
22 | 25 | 'ev-click': send(api.message.sheet.likes, likes) |
23 | - }, [`${likes.length} ${likes.length === 1 ? 'like' : 'likes'}`])] | |
26 | + }, [`${likes.length} ${likes.length === 1 ? i18n('like') : i18n('likes')}`])] | |
24 | 27 | } |
25 | 28 | } |
26 | 29 | |
27 | 30 | function names (ids) { |
28 | 31 | var items = map(ids, api.about.obs.name) |
29 | 32 | return computed([items], (names) => { |
30 | - return 'Liked by\n' + names.map((n) => `- ${n}`).join('\n') | |
33 | + return i18n('Liked by\n') + names.map((n) => `- ${n}`).join('\n') | |
31 | 34 | }) |
32 | 35 | } |
33 | 36 | } |
plugs/message/html/render/about.js | ||
---|---|---|
@@ -12,14 +12,16 @@ | ||
12 | 12 | }, |
13 | 13 | 'keys.sync.id': 'first', |
14 | 14 | 'profile.html.person': 'first', |
15 | 15 | 'about.obs.name': 'first', |
16 | - 'blob.sync.url': 'first' | |
16 | + 'blob.sync.url': 'first', | |
17 | + 'intl.sync.i18n': 'first', | |
17 | 18 | }) |
18 | 19 | |
19 | 20 | exports.gives = nest('message.html.render') |
20 | 21 | |
21 | 22 | exports.create = function (api) { |
23 | + const i18n = api.intl.sync.i18n | |
22 | 24 | return nest('message.html.render', function about (msg, opts) { |
23 | 25 | if (msg.value.content.type !== 'about') return |
24 | 26 | if (!ref.isFeed(msg.value.content.about)) return |
25 | 27 | |
@@ -32,20 +34,20 @@ | ||
32 | 34 | if (c.name) { |
33 | 35 | var target = api.profile.html.person(c.about, c.name) |
34 | 36 | miniContent.push(computed([self, api.about.obs.name(c.about), c.name], (self, a, b) => { |
35 | 37 | if (self) { |
36 | - return ['self identifies as "', target, '"'] | |
38 | + return [i18n('self identifies as "'), target, '"'] | |
37 | 39 | } else if (a === b) { |
38 | - return ['identified ', api.profile.html.person(c.about)] | |
40 | + return [i18n('identified '), api.profile.html.person(c.about)] | |
39 | 41 | } else { |
40 | - return ['identifies ', api.profile.html.person(c.about), ' as "', target, '"'] | |
42 | + return [i18n('identifies '), api.profile.html.person(c.about), i18n(' as "'), target, '"'] | |
41 | 43 | } |
42 | 44 | })) |
43 | 45 | } |
44 | 46 | |
45 | 47 | if (c.image) { |
46 | 48 | if (!miniContent.length) { |
47 | - var imageAction = self ? 'self assigned a display image' : ['assigned a display image to ', api.profile.html.person(c.about)] | |
49 | + var imageAction = self ? i18n('self assigned a display image') : [i18n('assigned a display image to '), api.profile.html.person(c.about)] | |
48 | 50 | miniContent.push(imageAction) |
49 | 51 | } |
50 | 52 | |
51 | 53 | content.push(h('a AboutImage', { |
@@ -69,9 +71,9 @@ | ||
69 | 71 | |
70 | 72 | if (c.description) { |
71 | 73 | elements.push(api.message.html.decorate(api.message.html.layout(msg, extend({ |
72 | 74 | showActions: true, |
73 | - miniContent: self ? 'self assigned a description' : ['assigned a description to ', api.profile.html.person(c.about)], | |
75 | + miniContent: self ? i18n('self assigned a description') : [i18n('assigned a description to '), api.profile.html.person(c.about)], | |
74 | 76 | content: api.message.html.markdown(c.description), |
75 | 77 | layout: 'mini' |
76 | 78 | }, opts)), { msg })) |
77 | 79 | } |
plugs/message/html/render/channel.js | ||
---|---|---|
@@ -5,14 +5,16 @@ | ||
5 | 5 | exports.needs = nest({ |
6 | 6 | 'message.html': { |
7 | 7 | decorate: 'reduce', |
8 | 8 | layout: 'first' |
9 | - } | |
9 | + }, | |
10 | + 'intl.sync.i18n':'first', | |
10 | 11 | }) |
11 | 12 | |
12 | 13 | exports.gives = nest('message.html.render') |
13 | 14 | |
14 | 15 | exports.create = function (api) { |
16 | + const i18n = api.intl.sync.i18n | |
15 | 17 | return nest('message.html.render', function renderMessage (msg, opts) { |
16 | 18 | if (msg.value.content.type !== 'channel') return |
17 | 19 | var element = api.message.html.layout(msg, extend({ |
18 | 20 | miniContent: messageContent(msg), |
@@ -25,9 +27,9 @@ | ||
25 | 27 | function messageContent (msg) { |
26 | 28 | var channel = `#${msg.value.content.channel}` |
27 | 29 | var subscribed = msg.value.content.subscribed |
28 | 30 | return [ |
29 | - subscribed ? 'subscribed to ' : 'unsubscribed from ', | |
31 | + subscribed ? i18n('subscribed to ') : i18n('unsubscribed from '), | |
30 | 32 | h('a', {href: channel}, channel) |
31 | 33 | ] |
32 | 34 | } |
33 | 35 | } |
plugs/message/html/render/following.js | ||
---|---|---|
@@ -7,14 +7,16 @@ | ||
7 | 7 | 'message.html': { |
8 | 8 | decorate: 'reduce', |
9 | 9 | layout: 'first' |
10 | 10 | }, |
11 | - 'profile.html.person': 'first' | |
11 | + 'profile.html.person': 'first', | |
12 | + 'intl.sync.i18n': 'first', | |
12 | 13 | }) |
13 | 14 | |
14 | 15 | exports.gives = nest('message.html.render') |
15 | 16 | |
16 | 17 | exports.create = function (api) { |
18 | + const i18n = api.intl.sync.i18n | |
17 | 19 | return nest('message.html.render', function renderMessage (msg, opts) { |
18 | 20 | if (msg.value.content.type !== 'contact') return |
19 | 21 | if (!ref.isFeed(msg.value.content.contact)) return |
20 | 22 | if (typeof msg.value.content.following !== 'boolean') return |
@@ -29,9 +31,9 @@ | ||
29 | 31 | |
30 | 32 | function messageContent (msg) { |
31 | 33 | var following = msg.value.content.following |
32 | 34 | return [ |
33 | - following ? 'followed ' : 'unfollowed ', | |
35 | + following ? i18n('followed ') : i18n('unfollowed '), | |
34 | 36 | api.profile.html.person(msg.value.content.contact) |
35 | 37 | ] |
36 | 38 | } |
37 | 39 | } |
plugs/intl/sync/i18n.js | ||
---|---|---|
@@ -1,0 +1,99 @@ | ||
1 | +const nest = require('depnest') | |
2 | +var { watch } = require('mutant') | |
3 | +var appRoot = require('app-root-path'); | |
4 | +var i18nL = require("i18n") | |
5 | + | |
6 | +exports.gives = nest('intl.sync', [ | |
7 | + 'locale', | |
8 | + 'locales', | |
9 | + 'i18n', | |
10 | + 'time', | |
11 | +]) | |
12 | + | |
13 | +exports.needs = nest({ | |
14 | + 'intl.sync.locale':'first', | |
15 | + 'intl.sync.locales':'reduce', | |
16 | + 'settings.obs.get': 'first', | |
17 | + 'settings.sync.set': 'first' | |
18 | +}) | |
19 | + | |
20 | +exports.create = (api) => { | |
21 | + let _locale | |
22 | + | |
23 | + const { | |
24 | + locale: getLocale, | |
25 | + locales: getLocales, | |
26 | + i18n: getI18n, | |
27 | + } = api.intl.sync | |
28 | + | |
29 | + return nest('intl.sync', { | |
30 | + locale, | |
31 | + locales, | |
32 | + i18n, | |
33 | + time | |
34 | + }) | |
35 | + | |
36 | + //Get locale value in setting | |
37 | + function locale () { | |
38 | + return api.settings.obs.get('patchwork.lang') | |
39 | + } | |
40 | + | |
41 | + //Get all locales loaded in i18nL | |
42 | + function locales (sofar = {}) { | |
43 | + return i18nL.getLocales() | |
44 | + } | |
45 | + | |
46 | + //Get translation | |
47 | + function i18n (value) { | |
48 | + _init() | |
49 | + return i18nL.__(value) | |
50 | + } | |
51 | + | |
52 | + function time (date){ | |
53 | + return date | |
54 | + .replace(/from now/, i18n('form now')) | |
55 | + .replace(/ago/, i18n('ago')) | |
56 | + .replace(/years/,i18n('years')) | |
57 | + .replace(/months/,i18n('months')) | |
58 | + .replace(/weeks/,i18n('weeks')) | |
59 | + .replace(/days/,i18n('days')) | |
60 | + .replace(/hours/,i18n('hours')) | |
61 | + .replace(/minutes/,i18n('minutes')) | |
62 | + .replace(/seconds/,i18n('seconds')) | |
63 | + .replace(/year/,i18n('year')) | |
64 | + .replace(/month/,i18n('month')) | |
65 | + .replace(/week/,i18n('week')) | |
66 | + .replace(/day/,i18n('day')) | |
67 | + .replace(/hour/,i18n('hour')) | |
68 | + .replace(/minute/,i18n('minute')) | |
69 | + .replace(/second/,i18n('second')) | |
70 | + } | |
71 | + | |
72 | + //Init an subscribe to settings changes. | |
73 | + function _init() { | |
74 | + if (_locale) return | |
75 | + //TODO: Depject this! | |
76 | + i18nL.configure({ | |
77 | + directory: appRoot + '/locales', | |
78 | + defaultLocale: 'en' | |
79 | + }); | |
80 | + | |
81 | + watch(api.settings.obs.get('patchwork.lang',navigator.language), currentLocale => { | |
82 | + i18nL.setLocale(getSubLocal(currentLocale)) | |
83 | + | |
84 | + // Only refresh if the language has already been selected once. | |
85 | + // This will prevent the update loop | |
86 | + if (_locale) { | |
87 | + electron.remote.getCurrentWebContents().reloadIgnoringCache() | |
88 | + } | |
89 | + }) | |
90 | + | |
91 | + _locale = true; | |
92 | + } | |
93 | + | |
94 | +} | |
95 | + | |
96 | +//For now get only global languages | |
97 | +function getSubLocal(loc) { | |
98 | + return loc.split('-')[0] | |
99 | +} |
locales/en.json | ||
---|---|---|
@@ -1,0 +1,136 @@ | ||
1 | +{ | |
2 | + "Patchwork": "Patchwork", | |
3 | + "Public": "Public", | |
4 | + "Private": "Private", | |
5 | + "Write a public message": "Write a public message", | |
6 | + "Active Channels": "Active Channels", | |
7 | + "Loading": "Loading", | |
8 | + "Local": "Local", | |
9 | + "Connected Pubs": "Connected Pubs", | |
10 | + "Who to follow": "Who to follow", | |
11 | + "Unsubscribe": "Unsubscribe", | |
12 | + "Subscribe": "Subscribe", | |
13 | + "Publishing...": "Publishing...", | |
14 | + "Publish": "Publish", | |
15 | + "Show ": "Show ", | |
16 | + "update": "update", | |
17 | + "updates": "updates", | |
18 | + "+ Join Pub": "+ Join Pub", | |
19 | + "word, @key, #channel": "word, @key, #channel", | |
20 | + "Profile": "Profile", | |
21 | + "Mentions": "Mentions", | |
22 | + " liked this message": " liked this message", | |
23 | + "View full thread": "View full thread", | |
24 | + " replied": " replied", | |
25 | + " replied to ": " replied to ", | |
26 | + "like": "like", | |
27 | + "Liked by\n": "Liked by\n", | |
28 | + " and ": " and ", | |
29 | + " followed ": " followed ", | |
30 | + " subscribed to ": " subscribed to ", | |
31 | + "likes": "likes", | |
32 | + " others": " others", | |
33 | + "Write a private reply": "Write a private reply", | |
34 | + "Write a public reply": "Write a public reply", | |
35 | + "More Channels...": "More Channels...", | |
36 | + "Cannot display message.": "Cannot display message.", | |
37 | + "All Posts from Your ": "All Posts from Your ", | |
38 | + "Click to unsubscribe": "Click to unsubscribe", | |
39 | + "Subscribed": "Subscribed", | |
40 | + "Write a message in this channel": "Write a message in this channel\n\n\n\nPeople who follow you or subscribe to this channel will also see this message in their main feed.\n\nTo create a new channel, type the channel name (preceded by a #) into the search box above. e.g #cat-pics", | |
41 | + "liked this message": "liked this message", | |
42 | + "replied to this message": "replied to this message", | |
43 | + "added changes": "added changes", | |
44 | + "mentioned you": "mentioned you", | |
45 | + "mentioned this channel": "mentioned this channel", | |
46 | + "Write a private message": "Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.", | |
47 | + "Edit Your Profile": "Edit Your Profile", | |
48 | + "Click to unfollow": "Click to unfollow", | |
49 | + "Friends": "Friends", | |
50 | + "Following": "Following", | |
51 | + "Follow Back": "Follow Back", | |
52 | + "Follow": "Follow", | |
53 | + "Followers": "Followers", | |
54 | + "More": "More", | |
55 | + "Gatherings": "Gatherings", | |
56 | + "Extended Network": "Extended Network", | |
57 | + "Settings": "Settings", | |
58 | + "Upgrading database": "Upgrading database", | |
59 | + "Downloading new messages": "Downloading new messages", | |
60 | + "Indexing database": "Indexing database", | |
61 | + "Scuttling...": "Scuttling...", | |
62 | + " has been released.": " has been released.", | |
63 | + " Click here to download and view more info!": " Click here to download and view more info!", | |
64 | + "Self Assigned": "Self Assigned", | |
65 | + "Assigned By": "Assigned By", | |
66 | + "self assigned a description": "self assigned a description", | |
67 | + "in reply to ": "in reply to ", | |
68 | + "subscribed to ": "subscribed to ", | |
69 | + "followed ": "followed ", | |
70 | + "identifies ": "identifies ", | |
71 | + " as \"": " as \"", | |
72 | + "paste invite code here": "paste invite code here", | |
73 | + "By default, Patchwork will only see other users that are on the same local area network as you.": "By default, Patchwork will only see other users that are on the same local area network as you.", | |
74 | + "In order to share with users on the internet, you need to be invited to a pub server.": "In order to share with users on the internet, you need to be invited to a pub server.", | |
75 | + "Redeem Invite": "Redeem Invite", | |
76 | + "Cancel": "Cancel", | |
77 | + "Channels": "Channels", | |
78 | + "Browse All": "Browse All", | |
79 | + " from your extended network": " from your extended network", | |
80 | + "+ Add Gathering": "+ Add Gathering", | |
81 | + " referenced this message:": " referenced this message:", | |
82 | + "Create": "Create", | |
83 | + " Gathering": " Gathering", | |
84 | + "Choose a title": "Choose a title", | |
85 | + "Choose date and time": "Choose date and time", | |
86 | + "Choose Banner Image...": "Choose Banner Image...", | |
87 | + "Describe the gathering (if you want)": "Describe the gathering (if you want)", | |
88 | + "Edit": "Edit", | |
89 | + "identified ": "identified ", | |
90 | + "self identifies as \"": "self identifies as \"", | |
91 | + "form now": "form now", | |
92 | + "ago": "ago", | |
93 | + "years": "years", | |
94 | + "months": "months", | |
95 | + "weeks": "weeks", | |
96 | + "days": "days", | |
97 | + "hours": "hours", | |
98 | + "minutes": "mins", | |
99 | + "seconds": "secs", | |
100 | + "year": "year", | |
101 | + "month": "month", | |
102 | + "week": "week", | |
103 | + "day": "day", | |
104 | + "hour": "hour", | |
105 | + "minute": "min", | |
106 | + "second": "sec", | |
107 | + "assigned a display image to ": "assigned a display image to ", | |
108 | + "Theme": "Theme", | |
109 | + "Language": "Language", | |
110 | + "Filters": "Filters", | |
111 | + " Hide following messages": " Hide following messages", | |
112 | + "Cannot display message" : "Cannot display message", | |
113 | + "Search Results:" : "Search Results:", | |
114 | + "Search completed." : "Search completed.", | |
115 | + "result found" : "result found", | |
116 | + "results found" : "results found", | |
117 | + " forked this discussion:" : " forked this discussion:", | |
118 | + "Your Profile" : "Your Profile", | |
119 | + "Choose Profile Image..." : "Choose Profile Image...", | |
120 | + "Choose a name" : "Choose a name", | |
121 | + "Describe yourself (if you want)" : "Describe yourself (if you want)", | |
122 | + "What whould you like to call " : "What whould you like to call ", | |
123 | + "Confirm" : "Confirm", | |
124 | + "self assigned a display image" : "self assigned a display image", | |
125 | + "Like" : "Like", | |
126 | + "Reply" : "Reply", | |
127 | + "unfollowed " : "unfollowed ", | |
128 | + "Untitled Gathering" : "Untitled Gathering", | |
129 | + "Error" : "Error", | |
130 | + "An error occurred while attempting to publish gathering." : "An error occurred while attempting to publish gathering.", | |
131 | + "An error occurred while attempting to redeem invite." : "An error occurred while attempting to redeem invite.", | |
132 | + "OK" : "OK", | |
133 | + "Close" : "Close", | |
134 | + "New Message" : "New Message", | |
135 | + "unsubscribed from " : "unsubscribed from " | |
136 | +} |
locales/es.json | ||
---|---|---|
@@ -1,0 +1,136 @@ | ||
1 | +{ | |
2 | + "Patchwork": "Patchwork", | |
3 | + "Public": "Público", | |
4 | + "Private": "Privado", | |
5 | + "Write a public message": "Escribe un mensaje públio", | |
6 | + "Active Channels": "Canales activos", | |
7 | + "Loading": "Cargando", | |
8 | + "Local": "Local", | |
9 | + "Connected Pubs": "Pubs conectados", | |
10 | + "Who to follow": "A quién seguir", | |
11 | + "Unsubscribe": "Anular suscripción", | |
12 | + "Subscribe": "Suscribir", | |
13 | + "Publishing...": "Publicando...", | |
14 | + "Publish": "Publicar", | |
15 | + "Show ": "Mostrar ", | |
16 | + "update": "actualización", | |
17 | + "updates": "actualizaciones", | |
18 | + "+ Join Pub": "+ Unirce a un Pub", | |
19 | + "word, @key, #channel": "palabra, @key, #canal", | |
20 | + "Profile": "Perfil", | |
21 | + "Mentions": "Menciones", | |
22 | + " liked this message": " le gustó este mensaje", | |
23 | + "View full thread": "Ver hilo completo", | |
24 | + " replied": " respondió", | |
25 | + " replied to ": " respondió a ", | |
26 | + "like": "Me gusta", | |
27 | + "Liked by\n": "Les gusta a\n", | |
28 | + " and ": " y ", | |
29 | + " followed ": " siguie a ", | |
30 | + "subscribed to ": "se suscribió a ", | |
31 | + " subscribed to ": "se suscribió a ", | |
32 | + "likes": "Me gusta", | |
33 | + " others": " más", | |
34 | + "Write a private reply": "Escriba una respuesta privada", | |
35 | + "Write a public reply": "Escriba una respuesta pública", | |
36 | + "Cannot display message": "No se puede mostrar el mensaje", | |
37 | + "Cannot display message.": "No se puede mostrar el mensaje.", | |
38 | + "More Channels...": "Más canales...", | |
39 | + "Click to unsubscribe": "Click para anular suscripción", | |
40 | + "Subscribed": "Suscrito", | |
41 | + "Write a message in this channel": "Escriba un mensaje en este canal \n\n\n\nLas personas que te siguen o esten suscritas a este canal también verán este mensaje en su feed principal.\n\nPara crear un nuevo canal, escribe el nombre del canal (precedido por un #) en el cuadro de búsqueda de arriba, por ejemplo #cat-pics", | |
42 | + "Gatherings": "Reuniones", | |
43 | + " from your extended network": " de su red extendida", | |
44 | + "+ Add Gathering": "+ Agregar Reunión", | |
45 | + "Write a private message": "Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.", | |
46 | + "Edit Your Profile": "Edite su perfil", | |
47 | + "Click to unfollow": "Click para dejar de seguir", | |
48 | + "Friends": "Amigos", | |
49 | + "Following": "Siguiendo", | |
50 | + "Follow Back": "Seguir de vuelta", | |
51 | + "Follow": "Seguir", | |
52 | + "Followers": "Seguidores", | |
53 | + "Self Assigned": "Auto asignado", | |
54 | + "Assigned By": "Asignado por", | |
55 | + "Search Results:": "Resultados de busqueda:", | |
56 | + "Search completed.": "Busqueda completada.", | |
57 | + "result found": "resultado encontrado", | |
58 | + "results found": "resultados encontrados", | |
59 | + "Settings": "Configuraciones", | |
60 | + "Theme": "Diseño", | |
61 | + "Filters": "Filtros", | |
62 | + " Hide following messages": " Ocultar mensajes de seguidores", | |
63 | + "Upgrading database": "Upgrading database", | |
64 | + "Downloading new messages": "Downloading new messages", | |
65 | + "Indexing database": "Indexing database", | |
66 | + "Scuttling...": "Scuttling...", | |
67 | + "Create": "Crear", | |
68 | + " Gathering": " Reunión", | |
69 | + "Choose a title": "Elige un título", | |
70 | + "Choose date and time": "Elige una fecha y hora", | |
71 | + "Choose Banner Image...": "Elige una imagen destacada...", | |
72 | + "Describe the gathering (if you want)": "Describa la reunión (si lo desea)", | |
73 | + "Cancel": "Cancelar", | |
74 | + "paste invite code here": "pegar el código de invitación", | |
75 | + "By default, Patchwork will only see other users that are on the same local area network as you.": "Por defecto Patchwork sólo verá a otros usuarios que estén en la misma red de área local que usted.", | |
76 | + "In order to share with users on the internet, you need to be invited to a pub server.": "Para compartir con los usuarios en Internet, es necesario ser invitado a un servidor de pub.", | |
77 | + "Redeem Invite": "Validar invitación", | |
78 | + " forked this discussion:": " bifurcó esta discucion:", | |
79 | + "All Posts from Your ": "Todos los mensajes de su ", | |
80 | + " referenced this message:": " hizo referencia a este mensaje:", | |
81 | + "Your Profile": "Su perfil", | |
82 | + "Choose Profile Image...": "Elegir imagen de visualización...", | |
83 | + "Choose a name": "Elige un nombre", | |
84 | + "Describe yourself (if you want)": "Descríbase (si quiere)", | |
85 | + "in reply to ": "en respuesta a ", | |
86 | + "self assigned a description": "se asignó una descripción", | |
87 | + "identifies ": "identificó a ", | |
88 | + "What whould you like to call ": "Cómo te gustaría llamar a ", | |
89 | + "Confirm": "Confirmar", | |
90 | + "self assigned a display image": "se asignó una imagen de visualización", | |
91 | + "self identifies as \"": "autoidentificado como \"", | |
92 | + "Like": "Me gusta", | |
93 | + "Reply": "Comentar", | |
94 | + "followed ": "sigue a ", | |
95 | + "identified ": "se denominó ", | |
96 | + "unfollowed ": "unfollowed ", | |
97 | + " as \"": " como \"", | |
98 | + "assigned a display image to ": "asignó una imagen de visualización a ", | |
99 | + "liked this message": "le gusto este mensaje", | |
100 | + "replied to this message": "comentó en este mensaje", | |
101 | + "added changes": "agregó cambios", | |
102 | + "mentioned you": "te mencionó", | |
103 | + "mentioned this channel": "mencionó este canal", | |
104 | + "Extended Network": "Red extendida", | |
105 | + " has been released.": " a sido publicada.", | |
106 | + " Click here to download and view more info!": " Haga clic aquí para descargar y ver más información!", | |
107 | + "Channels": "Canales", | |
108 | + "Browse All": "Ver todos", | |
109 | + "More": "Más", | |
110 | + "Edit": "Editar", | |
111 | + "Untitled Gathering": "Reunión sin título", | |
112 | + "Error": "Error", | |
113 | + "An error occurred while attempting to publish gathering.": "Se ha producido un error al intentar publicar la reunión", | |
114 | + "An error occurred while attempting to redeem invite.": "Se ha producido un error al intentar validar la invitación.", | |
115 | + "OK": "OK", | |
116 | + "Close": "Cerrar", | |
117 | + "New Message": "Nuevo mensaje", | |
118 | + "Language": "Idioma", | |
119 | + "form now": "en el futuro", | |
120 | + "ago": "atras", | |
121 | + "years": "años", | |
122 | + "months": "meses", | |
123 | + "weeks": "semanas", | |
124 | + "days": "días", | |
125 | + "hours": "horas", | |
126 | + "minutes": "minutos", | |
127 | + "seconds": "segundos", | |
128 | + "year": "año", | |
129 | + "month": "mes", | |
130 | + "week": "semana", | |
131 | + "day": "día", | |
132 | + "hour": "hora", | |
133 | + "minute": "minutos", | |
134 | + "second": "segundos", | |
135 | + "unsubscribed from ": "desuscrito de " | |
136 | +} |
locales/ki.json | ||
---|---|---|
@@ -1,0 +1,37 @@ | ||
1 | +{ | |
2 | + "Write a public message": "پبلک پیغام لکھنے", | |
3 | + "Active Channels": "فعال", | |
4 | + "Loading": "لوڈ ہو رہا ہے", | |
5 | + "Local": "مقامی", | |
6 | + "Connected Pubs": "مربوط عوامی مقامات", | |
7 | + "Who to follow": "جو پیروی کرنے کے لئے", | |
8 | + "Public": "عوام", | |
9 | + "Private": "نجی", | |
10 | + "Patchwork": "जन्द । नक्तकः", | |
11 | + "Unsubscribe": "رکنیت ختم", | |
12 | + "Subscribe": "سبسکرائب", | |
13 | + "+ Join Pub": "+ عوامی جگہ شمولیت", | |
14 | + "Profile": "تم", | |
15 | + "Mentions": "تذکرے", | |
16 | + "Show ": "شو ", | |
17 | + "update": "اپ ڈیٹ", | |
18 | + "updates": "اپ ڈیٹ", | |
19 | + " liked this message": " اس پیغام کو پسند کیا", | |
20 | + "View full thread": "مکمل دھاگے کو دیکھیں", | |
21 | + " replied": " جواب", | |
22 | + " subscribed to ": " سبسکرائب ", | |
23 | + " replied to ": " کا جواب دیا ", | |
24 | + " followed ": " کی پیروی کی ", | |
25 | + " and ": " اور ", | |
26 | + " others": " دوسروں", | |
27 | + " liked ": " پسند کیا ", | |
28 | + "word, @key, #channel": "لفظ, @کلید, #چینل", | |
29 | + "like": "کی طرح", | |
30 | + "Liked by\n": "پسند\n", | |
31 | + "likes": "پسند کرتا ہے", | |
32 | + "Write a private reply": "نجی جواب", | |
33 | + "Write a public reply": "پبلک جواب", | |
34 | + "Publishing...": "پبلشنگ ...", | |
35 | + "Publish": "شائع", | |
36 | + "More Channels...": "More Channels..." | |
37 | +} |
Built with git-ssb-web