Skip to content

Commit f2b1085

Browse files
authored
Added: language support (#270)
An environment variable LANG needs to contain a GNU posix locale for it to pick up the right language file. - Added a functional test for it too. - Now we look into other platforms also and show the page along with a warning message that this page is from a different platform. This follows the spec more closely.
1 parent 48d6c65 commit f2b1085

9 files changed

+232
-70
lines changed

.eslintrc.json

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"arrow-parens": [2, "always"],
99
"arrow-body-style": [2, "always"],
1010
"array-callback-return": 2,
11-
"complexity": [2, 10],
1211
"no-magic-numbers": [2, {
1312
"ignore": [-1, 0, 1, 2],
1413
"ignoreArrayIndexes": true,

README.md

+16-8
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ To see tldr pages:
4141
- `tldr <command> --os=<platform>` show command page for the given platform (`linux`, `osx`, `sunos`)
4242
- `tldr --search "<query>"` search all pages for the query
4343
- `tldr --linux <command>` show command page for Linux
44-
- `tldr --osx <command>` show command page for OSX
44+
- `tldr --osx <command>` show command page for OSX
4545
- `tldr --sunos <command>` show command page for SunOS
4646
- `tldr --list` show all pages for current platform
4747
- `tldr --list-all` show all available pages
@@ -59,6 +59,14 @@ As a contributor, you might also need the following commands:
5959

6060
- `tldr --render <path>` render a local page for testing purposes
6161

62+
Tldr pages defaults to showing pages in the current language of the operating system, or English if that's not available. To view tldr pages for a different language, set an environment variable `LANG` containing a valid [POSIX locale](https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html#Locale-Names) (such as `zh`, `pt_BR`, or `fr`) and then run the above commands as usual. In most `*nix` systems, this variable will already be set.
63+
64+
It is suggested that the `LANG` environment variable be set system-wide if this isn't already the case. Users without `sudo` access can set it locally in their `~/.profile`.
65+
66+
- `LANG=zh tldr <command>`
67+
68+
For the list of available translations, please refer to the main [tldr](https://github.com/tldr-pages/tldr) repo.
69+
6270
## Configuration
6371

6472
You can configure the `tldr` client by adding a `.tldrrc` file in your HOME directory. You can copy the contents of the `config.json` file from the repo to get the basic structure to start with, and modify it to suit your needs.
@@ -147,25 +155,25 @@ fpath = (my/completions $fpath)
147155

148156
#### Installation Issues
149157

150-
- If you are trying to install as non-root user (`npm install -g tldr`) and get something like -
151-
158+
- If you are trying to install as non-root user (`npm install -g tldr`) and get something like:
159+
152160
```
153161
Error: EACCES: permission denied, access '/usr/local/lib/node_modules/tldr'
154162
```
155-
163+
156164
Then most probably your npm's default installation directory has improper permissions. You can resolve it by clicking [here](https://docs.npmjs.com/getting-started/fixing-npm-permissions)
157-
158-
- If you are trying to install as a root user (`sudo npm install -g tldr`) and get something like -
165+
166+
- If you are trying to install as a root user (`sudo npm install -g tldr`) and get something like:
159167

160168
```
161-
as root ->
169+
as root ->
162170
gyp WARN EACCES attempting to reinstall using temporary dev dir "/usr/local/lib/node_modules/tldr/node_modules/webworker-threads/.node-gyp"
163171
gyp WARN EACCES user "root" does not have permission to access the dev dir "/usr/local/lib/node_modules/tldr/node_modules/webworker-threads/.node-gyp/8.9.1"
164172
```
165173

166174
You need to add the option `--unsafe-perm` to your command. This is because when npm goes to the postinstall step, it downgrades the permission levels to "nobody". Probably you should fix your installation directory permissions and install as a non-root user in the first place.
167175

168-
- If you see an error related to `webworker-threads` like -
176+
- If you see an error related to `webworker-threads` like:
169177

170178
```
171179
/usr/local/lib/node_modules/tldr/node_modules/natural/lib/natural/classifiers/classifier.js:32

lib/cache.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ class Cache {
2222

2323
getPage(page) {
2424
let preferredPlatform = platform.getPreferredPlatformFolder(this.config);
25-
return index.findPlatform(page, preferredPlatform)
25+
let preferredLanguage = 'en';
26+
if (process.env['LANG'] !== '') {
27+
preferredLanguage = process.env['LANG'];
28+
}
29+
return index.findPage(page, preferredPlatform, preferredLanguage)
2630
.then((folder) => {
2731
if (!folder) {
2832
return;
2933
}
30-
let filePath = path.join(this.cacheFolder, 'pages', folder, page + '.md');
34+
let filePath = path.join(this.cacheFolder, folder, page + '.md');
3135
return fs.readFile(filePath, 'utf8');
3236
})
3337
.catch((err) => {

lib/index.js

+90-13
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,90 @@ const utils = require('./utils');
77

88
let shortIndex = null;
99

10-
const pagesPath = path.join(config.get().cache, 'cache/pages');
10+
const pagesPath = path.join(config.get().cache, 'cache');
1111
const shortIndexFile = path.join(pagesPath, 'shortIndex.json');
1212

13-
function findPlatform(page, preferredPlatform) {
13+
function findPage(page, preferredPlatform, preferredLanguage) {
1414
// Load the index
1515
return getShortIndex()
1616
.then((idx) => {
1717
// First, check whether page is in the index
1818
if (! (page in idx)) {
1919
return null;
2020
}
21-
// Get the platforms
22-
let platforms = idx[page];
23-
if (platforms.indexOf(preferredPlatform) >= 0) {
24-
return preferredPlatform;
25-
} else if (platforms.indexOf('common') >= 0) {
26-
return 'common';
21+
const targets = idx[page].targets;
22+
23+
// Remove unwanted stuff from lang code.
24+
if (preferredLanguage.includes('.')) {
25+
preferredLanguage = preferredLanguage.substring(0, preferredLanguage.indexOf('.'));
26+
}
27+
if (preferredLanguage.includes('@')) {
28+
preferredLanguage = preferredLanguage.substring(0, preferredLanguage.indexOf('@'));
29+
}
30+
31+
let ll;
32+
if (preferredLanguage.includes('_')) {
33+
ll = preferredLanguage.substring(0, preferredLanguage.indexOf('_'));
34+
}
35+
if (!hasLang(targets, preferredLanguage)) {
36+
preferredLanguage = ll;
37+
}
38+
39+
// Page resolution logic:
40+
// 1. Look into the target platform, target lang
41+
// 2. If not found, look into target platform, en lang.
42+
// 3. If not found, look into common, target lang.
43+
// 4. If not found, look into common, en lang.
44+
// 5. If not found, look into any platform, target lang.
45+
// 6. If not found, look into any platform, en lang.
46+
let targetPlatform;
47+
let targetLanguage;
48+
if (hasPlatformLang(targets, preferredPlatform, preferredLanguage)) {
49+
targetLanguage = preferredLanguage;
50+
targetPlatform = preferredPlatform;
51+
} else if (hasPlatformLang(targets, preferredPlatform, 'en')) {
52+
targetLanguage = 'en';
53+
targetPlatform = preferredPlatform;
54+
} else if (hasPlatformLang(targets, 'common', preferredLanguage)) {
55+
targetLanguage = preferredLanguage;
56+
targetPlatform = 'common';
57+
} else if (hasPlatformLang(targets, 'common', 'en')) {
58+
targetLanguage = 'en';
59+
targetPlatform = 'common';
60+
} else if (targets.length > 0 && hasLang(targets, preferredLanguage)) {
61+
targetLanguage = preferredLanguage;
62+
targetPlatform = targets[0].os;
63+
console.log(`Command ${page} does not exist for the host platform. Displaying the page from ${targets[0].os} platform`);
64+
} else if (targets.length > 0 && hasLang(targets, 'en')) {
65+
targetLanguage = 'en';
66+
targetPlatform = targets[0].os;
67+
console.log(`Command ${page} does not exist for the host platform. Displaying the page from ${targets[0].os} platform`);
68+
}
69+
70+
if (!targetLanguage && !targetPlatform) {
71+
return null;
2772
}
28-
return null;
73+
74+
let targetPath = 'pages';
75+
if (targetLanguage !== 'en') {
76+
targetPath += '.' + targetLanguage;
77+
}
78+
return path.join(targetPath, targetPlatform);
2979
});
3080
}
3181

82+
function hasPlatformLang(targets, preferredPlatform, preferredLanguage) {
83+
return targets.some((t) => {
84+
return t.os === preferredPlatform && t.language === preferredLanguage;
85+
});
86+
}
87+
88+
function hasLang(targets, preferredLanguage) {
89+
return targets.some((t) => {
90+
return t.language === preferredLanguage;
91+
});
92+
}
93+
3294
// hasPage is always called after the index is created,
3395
// hence just return the variable in memory.
3496
// There is no need to re-read the index file again.
@@ -53,7 +115,9 @@ function commandsFor(platform) {
53115
.then((idx) => {
54116
let commands = Object.keys(idx)
55117
.filter((cmd) => {
56-
return idx[cmd].indexOf(platform) !== -1 || idx[cmd].indexOf('common') !== -1;
118+
let targets = idx[cmd].targets;
119+
let platforms = targets.map((t) => {return t.os;});
120+
return platforms.indexOf(platform) !== -1 || platforms.indexOf('common') !== -1;
57121
})
58122
.sort();
59123
return commands;
@@ -125,11 +189,24 @@ function buildShortPagesIndex() {
125189
let reducer = (index, file) => {
126190
let os = utils.parsePlatform(file);
127191
let page = utils.parsePagename(file);
192+
let language = utils.parseLanguage(file);
128193
if (index[page]) {
129-
index[page].push(os);
194+
let targets = index[page].targets;
195+
let needsPush = true;
196+
for (const target of targets) {
197+
if (target.platform === os && target.language === language) {
198+
needsPush = false;
199+
continue;
200+
}
201+
}
202+
if (needsPush) {
203+
targets.push({'os': os, 'language': language});
204+
index[page].targets = targets;
205+
}
130206
} else {
131-
index[page] = [os];
207+
index[page] = {targets: [{'os': os, 'language': language}]};
132208
}
209+
133210
return index;
134211
};
135212
return files.reduce(reducer, {});
@@ -142,7 +219,7 @@ function buildShortPagesIndex() {
142219
module.exports = {
143220
getShortIndex,
144221
hasPage,
145-
findPlatform,
222+
findPage,
146223
commands,
147224
commandsFor,
148225
clearPagesIndex,

lib/search.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ exports.printResults = (results, config) => {
197197
results.forEach((elem) => {
198198
let cmdname = utils.parsePagename(elem.file);
199199
let output = ' $ ' + cmdname;
200-
let array = shortIndex[cmdname];
200+
let array = shortIndex[cmdname]['platforms'];
201201
if (array.indexOf('common') === -1 && array.indexOf(preferredPlatform) === -1) {
202202
output += ' (Available on: ' + array.join(', ') + ')';
203203
}

lib/utils.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ const flatten = require('lodash/flatten');
66

77
module.exports = {
88
parsePlatform(pagefile) {
9-
return path.dirname(pagefile);
9+
const components = pagefile.split(path.sep);
10+
return components[components.length-2];
1011
},
1112

1213
parsePagename(pagefile) {
1314
return path.basename(pagefile, '.md');
1415
},
1516

17+
parseLanguage(pagefile) {
18+
const components = pagefile.split(path.sep);
19+
const langPathIndex = 3;
20+
const langParts = components[components.length-langPathIndex].split('.');
21+
if (langParts.length === 1) {
22+
return 'en';
23+
}
24+
return langParts[1];
25+
},
26+
1627
isPage(file) {
1728
return path.extname(file) === '.md';
1829
},
@@ -34,7 +45,7 @@ module.exports = {
3445
if (stat.isDirectory()) {
3546
return walk(itemPath);
3647
}
37-
return path.join(path.basename(dir), item);
48+
return path.join(dir, item);
3849
});
3950
}));
4051
})

test/cache.spec.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -95,44 +95,44 @@ describe('Cache', () => {
9595
it('should return page contents for ls', () => {
9696
sinon.stub(fs, 'readFile').resolves('# ls\n> ls page');
9797
sinon.stub(platform, 'getPreferredPlatformFolder').returns('osx');
98-
sinon.stub(index, 'findPlatform').resolves('osx');
98+
sinon.stub(index, 'findPage').resolves('osx');
9999
const cache = new Cache(config.get());
100100
return cache.getPage('ls')
101101
.then((content) => {
102102
should.exist(content);
103103
content.should.startWith('# ls');
104104
fs.readFile.restore();
105105
platform.getPreferredPlatformFolder.restore();
106-
index.findPlatform.restore();
106+
index.findPage.restore();
107107
});
108108
});
109109

110110
it('should return empty contents for svcs on OSX', () =>{
111111
sinon.stub(fs, 'readFile').resolves('# svcs\n> svcs');
112112
sinon.stub(platform, 'getPreferredPlatformFolder').returns('osx');
113-
sinon.stub(index, 'findPlatform').resolves(null);
113+
sinon.stub(index, 'findPage').resolves(null);
114114
const cache = new Cache(config.get());
115115
return cache.getPage('svc')
116116
.then((content) => {
117117
should.not.exist(content);
118118
fs.readFile.restore();
119119
platform.getPreferredPlatformFolder.restore();
120-
index.findPlatform.restore();
120+
index.findPage.restore();
121121
});
122122
});
123123

124124
it('should return page contents for svcs on SunOS', () => {
125125
sinon.stub(fs, 'readFile').resolves('# svcs\n> svcs');
126126
sinon.stub(platform, 'getPreferredPlatformFolder').returns('sunos');
127-
sinon.stub(index, 'findPlatform').resolves('svcs');
127+
sinon.stub(index, 'findPage').resolves('svcs');
128128
const cache = new Cache(config.get());
129129
return cache.getPage('svcs')
130130
.then((content) => {
131131
should.exist(content);
132132
content.should.startWith('# svcs');
133133
fs.readFile.restore();
134134
platform.getPreferredPlatformFolder.restore();
135-
index.findPlatform.restore();
135+
index.findPage.restore();
136136
});
137137
});
138138

test/functional-test.sh

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ function tldr-render-pages {
1010
tldr du --os=osx && \
1111
tldr du --os=linux --markdown && \
1212
tldr du --os=osx --markdown && \
13-
tldr --random && \
14-
tldr --random-example && \
13+
LANG= tldr --random && \
14+
LANG= tldr --random-example && \
1515
tldr --list && \
1616
tldr --list-all
1717
}
1818

1919
tldr --render $HOME/.tldr/cache/pages/common/ssh.md && \
2020
tldr --update && tldr-render-pages && \
2121
tldr --clear-cache && \
22-
tldr --update && tldr-render-pages
22+
tldr --update && tldr-render-pages && \
23+
LANG=pt_BR tldr-render-pages && \
24+
tldr --search "disk space"

0 commit comments

Comments
 (0)