xref: /haiku/src/kits/locale/DefaultCatalog.cpp (revision 19ae20e67e91fc09cc9fc5c0e60e21e24e7a53eb)
1 /*
2  * Copyright 2003-2009, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Oliver Tappe, zooey@hirschkaefer.de
7  *		Adrien Destugues, pulkomandy@gmail.com
8  */
9 
10 
11 #include <memory>
12 #include <new>
13 #include <syslog.h>
14 
15 #include <AppFileInfo.h>
16 #include <Application.h>
17 #include <DataIO.h>
18 #include <Directory.h>
19 #include <File.h>
20 #include <FindDirectory.h>
21 #include <fs_attr.h>
22 #include <Message.h>
23 #include <Mime.h>
24 #include <Path.h>
25 #include <Resources.h>
26 #include <Roster.h>
27 
28 #include <DefaultCatalog.h>
29 #include <MutableLocaleRoster.h>
30 
31 #include <cstdio>
32 
33 
34 using std::auto_ptr;
35 using std::min;
36 using std::max;
37 using std::pair;
38 
39 
40 /*!	This file implements the default catalog-type for the opentracker locale
41 	kit. Alternatively, this could be used as a full add-on, but currently this
42 	is provided as part of liblocale.so.
43 */
44 
45 
46 static const char *kCatFolder = "catalogs";
47 static const char *kCatExtension = ".catalog";
48 
49 const char *DefaultCatalog::kCatMimeType
50 	= "locale/x-vnd.Be.locale-catalog.default";
51 
52 static int16 kCatArchiveVersion = 1;
53 	// version of the catalog archive structure, bump this if you change it!
54 
55 const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1;
56 	// give highest priority to our embedded catalog-add-on
57 
58 
59 /*!	Constructs a DefaultCatalog with given signature and language and reads
60 	the catalog from disk.
61 	InitCheck() will be B_OK if catalog could be loaded successfully, it will
62 	give an appropriate error-code otherwise.
63 */
64 DefaultCatalog::DefaultCatalog(const entry_ref &catalogOwner, const char *language,
65 	uint32 fingerprint)
66 	:
67 	BHashMapCatalog("", language, fingerprint)
68 {
69 	// We created the catalog with an invalid signature, but we fix that now.
70 	SetSignature(catalogOwner);
71 	status_t status;
72 
73 	// search for catalog living in sub-folder of app's folder:
74 	node_ref nref;
75 	nref.device = catalogOwner.device;
76 	nref.node = catalogOwner.directory;
77 	BDirectory appDir(&nref);
78 	BString catalogName("locale/");
79 	catalogName << kCatFolder
80 		<< "/" << fSignature
81 		<< "/" << fLanguageName
82 		<< kCatExtension;
83 	BPath catalogPath(&appDir, catalogName.String());
84 	status = ReadFromFile(catalogPath.Path());
85 
86 	if (status != B_OK) {
87 		// search in data folders
88 
89 		directory_which which[] = {
90 			B_USER_DATA_DIRECTORY,
91 			B_COMMON_DATA_DIRECTORY,
92 			B_SYSTEM_DATA_DIRECTORY
93 		};
94 
95 		for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
96 			BPath path;
97 			if (find_directory(which[i], &path) == B_OK) {
98 				BString catalogName(path.Path());
99 				catalogName << "/locale/" << kCatFolder
100 					<< "/" << fSignature
101 					<< "/" << fLanguageName
102 					<< kCatExtension;
103 				status = ReadFromFile(catalogName.String());
104 				if (status == B_OK)
105 					break;
106 			}
107 		}
108 	}
109 
110 	if (status != B_OK) {
111 		// give lowest priority to catalog embedded as resource in application
112 		// executable, so they can be overridden easily.
113 		status = ReadFromResource(catalogOwner);
114 	}
115 
116 	fInitCheck = status;
117 	log_team(LOG_DEBUG,
118 		"trying to load default-catalog(sig=%s, lang=%s) results in %s",
119 		fSignature.String(), language, strerror(fInitCheck));
120 }
121 
122 
123 /*!	Constructs a DefaultCatalog and reads it from the resources of the
124 	given entry-ref (which usually is an app- or add-on-file).
125 	InitCheck() will be B_OK if catalog could be loaded successfully, it will
126 	give an appropriate error-code otherwise.
127 */
128 DefaultCatalog::DefaultCatalog(entry_ref *appOrAddOnRef)
129 	:
130 	BHashMapCatalog("", "", 0)
131 {
132 	fInitCheck = ReadFromResource(*appOrAddOnRef);
133 	log_team(LOG_DEBUG,
134 		"trying to load embedded catalog from resources results in %s",
135 		strerror(fInitCheck));
136 }
137 
138 
139 /*!	Constructs an empty DefaultCatalog with given sig and language.
140 	This is used for editing/testing purposes.
141 	InitCheck() will always be B_OK.
142 */
143 DefaultCatalog::DefaultCatalog(const char *path, const char *signature,
144 	const char *language)
145 	:
146 	BHashMapCatalog(signature, language, 0),
147 	fPath(path)
148 {
149 	fInitCheck = B_OK;
150 }
151 
152 
153 DefaultCatalog::~DefaultCatalog()
154 {
155 }
156 
157 
158 void
159 DefaultCatalog::SetSignature(const entry_ref &catalogOwner)
160 {
161 	// figure out mimetype from image
162 	BFile objectFile(&catalogOwner, B_READ_ONLY);
163 	BAppFileInfo objectInfo(&objectFile);
164 	char objectSignature[B_MIME_TYPE_LENGTH];
165 	if (objectInfo.GetSignature(objectSignature) != B_OK) {
166 		log_team(LOG_ERR, "File %s has no mimesignature, so it can't use"
167 			" localization.", catalogOwner.name);
168 		fSignature = "";
169 		return;
170 	}
171 
172 	// drop supertype from mimetype (should be "application/"):
173 	char* stripSignature = objectSignature;
174 	while (*stripSignature != '/' && *stripSignature != '\0')
175 		stripSignature ++;
176 
177 	if (*stripSignature == '\0')
178 		stripSignature = objectSignature;
179 	else
180 		stripSignature ++;
181 
182 	log_team(LOG_DEBUG, "Image %s requested catalog with mimetype %s",
183 		catalogOwner.name, stripSignature);
184 	fSignature = stripSignature;
185 }
186 
187 
188 status_t
189 DefaultCatalog::SetRawString(const CatKey& key, const char *translated)
190 {
191 	return fCatMap.Put(key, translated);
192 }
193 
194 
195 status_t
196 DefaultCatalog::ReadFromFile(const char *path)
197 {
198 	if (!path)
199 		path = fPath.String();
200 
201 	BFile catalogFile;
202 	status_t res = catalogFile.SetTo(path, B_READ_ONLY);
203 	if (res != B_OK) {
204 		log_team(LOG_DEBUG, "LocaleKit DefaultCatalog: no catalog at %s", path);
205 		return B_ENTRY_NOT_FOUND;
206 	}
207 
208 	fPath = path;
209 	log_team(LOG_DEBUG, "LocaleKit DefaultCatalog: found catalog at %s", path);
210 
211 	off_t sz = 0;
212 	res = catalogFile.GetSize(&sz);
213 	if (res != B_OK) {
214 		log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't get size for "
215 			"catalog-file %s", path);
216 		return res;
217 	}
218 
219 	auto_ptr<char> buf(new(std::nothrow) char [sz]);
220 	if (buf.get() == NULL) {
221 		log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't allocate array "
222 			"of %d chars", sz);
223 		return B_NO_MEMORY;
224 	}
225 	res = catalogFile.Read(buf.get(), sz);
226 	if (res < B_OK) {
227 		log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't read from "
228 			"catalog-file %s", path);
229 		return res;
230 	}
231 	if (res < sz) {
232 		log_team(LOG_ERR,
233 			"LocaleKit DefaultCatalog: only got %lu instead of %Lu bytes from "
234 			"catalog-file %s", res, sz, path);
235 		return res;
236 	}
237 	BMemoryIO memIO(buf.get(), sz);
238 	res = Unflatten(&memIO);
239 
240 	if (res == B_OK) {
241 		// some information living in member variables needs to be copied
242 		// to attributes. Although these attributes should have been written
243 		// when creating the catalog, we make sure that they exist there:
244 		UpdateAttributes(catalogFile);
245 	}
246 
247 	return res;
248 }
249 
250 
251 /*
252  * this method is not currently being used, but it may be useful in the future...
253  */
254 status_t
255 DefaultCatalog::ReadFromAttribute(const entry_ref &appOrAddOnRef)
256 {
257 	BNode node;
258 	status_t res = node.SetTo(&appOrAddOnRef);
259 	if (res != B_OK) {
260 		log_team(LOG_ERR,
261 			"couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)",
262 			appOrAddOnRef.device, appOrAddOnRef.directory,
263 			appOrAddOnRef.name);
264 		return B_ENTRY_NOT_FOUND;
265 	}
266 
267 	log_team(LOG_DEBUG,
268 		"looking for embedded catalog-attribute in app/add-on"
269 		"(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef.device,
270 		appOrAddOnRef.directory, appOrAddOnRef.name);
271 
272 	attr_info attrInfo;
273 	res = node.GetAttrInfo(BLocaleRoster::kEmbeddedCatAttr, &attrInfo);
274 	if (res != B_OK) {
275 		log_team(LOG_DEBUG, "no embedded catalog found");
276 		return B_NAME_NOT_FOUND;
277 	}
278 	if (attrInfo.type != B_MESSAGE_TYPE) {
279 		log_team(LOG_ERR, "attribute %s has incorrect type and is ignored!",
280 			BLocaleRoster::kEmbeddedCatAttr);
281 		return B_BAD_TYPE;
282 	}
283 
284 	size_t size = attrInfo.size;
285 	auto_ptr<char> buf(new(std::nothrow) char [size]);
286 	if (buf.get() == NULL) {
287 		log_team(LOG_ERR, "couldn't allocate array of %d chars", size);
288 		return B_NO_MEMORY;
289 	}
290 	res = node.ReadAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0,
291 		buf.get(), size);
292 	if (res < (ssize_t)size) {
293 		log_team(LOG_ERR, "unable to read embedded catalog from attribute");
294 		return res < B_OK ? res : B_BAD_DATA;
295 	}
296 
297 	BMemoryIO memIO(buf.get(), size);
298 	res = Unflatten(&memIO);
299 
300 	return res;
301 }
302 
303 
304 status_t
305 DefaultCatalog::ReadFromResource(const entry_ref &appOrAddOnRef)
306 {
307 	BFile file;
308 	status_t res = file.SetTo(&appOrAddOnRef, B_READ_ONLY);
309 	if (res != B_OK) {
310 		log_team(LOG_ERR,
311 			"couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)",
312 			appOrAddOnRef.device, appOrAddOnRef.directory,
313 			appOrAddOnRef.name);
314 		return B_ENTRY_NOT_FOUND;
315 	}
316 
317 	log_team(LOG_DEBUG,
318 		"looking for embedded catalog-resource in app/add-on"
319 		"(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef.device,
320 		appOrAddOnRef.directory, appOrAddOnRef.name);
321 
322 	BResources rsrc;
323 	res = rsrc.SetTo(&file);
324 	if (res != B_OK) {
325 		log_team(LOG_DEBUG, "file has no resources");
326 		return res;
327 	}
328 
329 	int mangledLanguage = CatKey::HashFun(fLanguageName.String(), 0);
330 
331 	size_t sz;
332 	const void *buf = rsrc.LoadResource('CADA',
333 		mangledLanguage, &sz);
334 	if (!buf) {
335 		log_team(LOG_DEBUG, "file has no catalog-resource");
336 		return B_NAME_NOT_FOUND;
337 	}
338 
339 	BMemoryIO memIO(buf, sz);
340 	res = Unflatten(&memIO);
341 
342 	return res;
343 }
344 
345 
346 status_t
347 DefaultCatalog::WriteToFile(const char *path)
348 {
349 	BFile catalogFile;
350 	if (path)
351 		fPath = path;
352 	status_t res = catalogFile.SetTo(fPath.String(),
353 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
354 	if (res != B_OK)
355 		return res;
356 
357 	BMallocIO mallocIO;
358 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
359 		// set a largish block-size in order to avoid reallocs
360 	res = Flatten(&mallocIO);
361 	if (res == B_OK) {
362 		ssize_t wsz;
363 		wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength());
364 		if (wsz != (ssize_t)mallocIO.BufferLength())
365 			return B_FILE_ERROR;
366 
367 		// set mimetype-, language- and signature-attributes:
368 		UpdateAttributes(catalogFile);
369 	}
370 	if (res == B_OK)
371 		UpdateAttributes(catalogFile);
372 	return res;
373 }
374 
375 
376 /*
377  * this method is not currently being used, but it may be useful in the
378  * future...
379  */
380 status_t
381 DefaultCatalog::WriteToAttribute(const entry_ref &appOrAddOnRef)
382 {
383 	BNode node;
384 	status_t res = node.SetTo(&appOrAddOnRef);
385 	if (res != B_OK)
386 		return res;
387 
388 	BMallocIO mallocIO;
389 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
390 		// set a largish block-size in order to avoid reallocs
391 	res = Flatten(&mallocIO);
392 
393 	if (res == B_OK) {
394 		ssize_t wsz;
395 		wsz = node.WriteAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0,
396 			mallocIO.Buffer(), mallocIO.BufferLength());
397 		if (wsz < B_OK)
398 			res = wsz;
399 		else if (wsz != (ssize_t)mallocIO.BufferLength())
400 			res = B_ERROR;
401 	}
402 	return res;
403 }
404 
405 
406 status_t
407 DefaultCatalog::WriteToResource(const entry_ref &appOrAddOnRef)
408 {
409 	BFile file;
410 	status_t res = file.SetTo(&appOrAddOnRef, B_READ_WRITE);
411 	if (res != B_OK)
412 		return res;
413 
414 	BResources rsrc;
415 	res = rsrc.SetTo(&file);
416 	if (res != B_OK)
417 		return res;
418 
419 	BMallocIO mallocIO;
420 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
421 		// set a largish block-size in order to avoid reallocs
422 	res = Flatten(&mallocIO);
423 
424 	int mangledLanguage = CatKey::HashFun(fLanguageName.String(), 0);
425 
426 	if (res == B_OK) {
427 		res = rsrc.AddResource('CADA', mangledLanguage,
428 			mallocIO.Buffer(), mallocIO.BufferLength(),
429 			BString(fLanguageName) << " catalog");
430 	}
431 
432 	return res;
433 }
434 
435 
436 /*!	Writes mimetype, language-name and signature of catalog into the
437 	catalog-file.
438 */
439 void
440 DefaultCatalog::UpdateAttributes(BFile& catalogFile)
441 {
442 	static const int bufSize = 256;
443 	char buf[bufSize];
444 	uint32 temp;
445 	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf,
446 			bufSize) <= 0
447 		|| strcmp(kCatMimeType, buf) != 0) {
448 		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
449 			kCatMimeType, strlen(kCatMimeType)+1);
450 	}
451 	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
452 			&buf, bufSize) <= 0
453 		|| fLanguageName != buf) {
454 		catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
455 			fLanguageName.String(), fLanguageName.Length()+1);
456 	}
457 	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
458 			&buf, bufSize) <= 0
459 		|| fSignature != buf) {
460 		catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
461 			fSignature.String(), fSignature.Length()+1);
462 	}
463 	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
464 		0, &temp, sizeof(uint32)) <= 0) {
465 		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
466 			0, &fFingerprint, sizeof(uint32));
467 	}
468 }
469 
470 
471 status_t
472 DefaultCatalog::Flatten(BDataIO *dataIO)
473 {
474 	UpdateFingerprint();
475 		// make sure we have the correct fingerprint before we flatten it
476 
477 	status_t res;
478 	BMessage archive;
479 	int32 count = fCatMap.Size();
480 	res = archive.AddString("class", "DefaultCatalog");
481 	if (res == B_OK)
482 		res = archive.AddInt32("c:sz", count);
483 	if (res == B_OK)
484 		res = archive.AddInt16("c:ver", kCatArchiveVersion);
485 	if (res == B_OK)
486 		res = archive.AddString("c:lang", fLanguageName.String());
487 	if (res == B_OK)
488 		res = archive.AddString("c:sig", fSignature.String());
489 	if (res == B_OK)
490 		res = archive.AddInt32("c:fpr", fFingerprint);
491 	if (res == B_OK)
492 		res = archive.Flatten(dataIO);
493 
494 	CatMap::Iterator iter = fCatMap.GetIterator();
495 	CatMap::Entry entry;
496 	while (res == B_OK && iter.HasNext()) {
497 		entry = iter.Next();
498 		archive.MakeEmpty();
499 		res = archive.AddString("c:ostr", entry.key.fString.String());
500 		if (res == B_OK)
501 			res = archive.AddString("c:ctxt", entry.key.fContext.String());
502 		if (res == B_OK)
503 			res = archive.AddString("c:comt", entry.key.fComment.String());
504 		if (res == B_OK)
505 			res = archive.AddInt32("c:hash", entry.key.fHashVal);
506 		if (res == B_OK)
507 			res = archive.AddString("c:tstr", entry.value.String());
508 		if (res == B_OK)
509 			res = archive.Flatten(dataIO);
510 	}
511 
512 	return res;
513 }
514 
515 
516 status_t
517 DefaultCatalog::Unflatten(BDataIO *dataIO)
518 {
519 	fCatMap.Clear();
520 	int32 count = 0;
521 	int16 version;
522 	BMessage archiveMsg;
523 	status_t res = archiveMsg.Unflatten(dataIO);
524 
525 	if (res == B_OK) {
526 		res = archiveMsg.FindInt16("c:ver", &version)
527 			|| archiveMsg.FindInt32("c:sz", &count);
528 	}
529 	if (res == B_OK) {
530 		fLanguageName = archiveMsg.FindString("c:lang");
531 		fSignature = archiveMsg.FindString("c:sig");
532 		uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr");
533 
534 		// if a specific fingerprint has been requested and the catalog does in
535 		// fact have a fingerprint, both are compared. If they mismatch, we do
536 		// not accept this catalog:
537 		if (foundFingerprint != 0 && fFingerprint != 0
538 			&& foundFingerprint != fFingerprint) {
539 			log_team(LOG_INFO, "default-catalog(sig=%s, lang=%s) "
540 				"has mismatching fingerprint (%ld instead of the requested %ld), "
541 				"so this catalog is skipped.",
542 				fSignature.String(), fLanguageName.String(), foundFingerprint,
543 				fFingerprint);
544 			res = B_MISMATCHED_VALUES;
545 		} else
546 			fFingerprint = foundFingerprint;
547 	}
548 
549 	if (res == B_OK && count > 0) {
550 		CatKey key;
551 		const char *keyStr;
552 		const char *keyCtx;
553 		const char *keyCmt;
554 		const char *translated;
555 
556 		// fCatMap.resize(count);
557 			// There is no resize method in Haiku's HashMap to preallocate
558 			// memory.
559 		for (int i=0; res == B_OK && i < count; ++i) {
560 			res = archiveMsg.Unflatten(dataIO);
561 			if (res == B_OK)
562 				res = archiveMsg.FindString("c:ostr", &keyStr);
563 			if (res == B_OK)
564 				res = archiveMsg.FindString("c:ctxt", &keyCtx);
565 			if (res == B_OK)
566 				res = archiveMsg.FindString("c:comt", &keyCmt);
567 			if (res == B_OK)
568 				res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal);
569 			if (res == B_OK)
570 				res = archiveMsg.FindString("c:tstr", &translated);
571 			if (res == B_OK) {
572 				key.fString = keyStr;
573 				key.fContext = keyCtx;
574 				key.fComment = keyCmt;
575 				fCatMap.Put(key, translated);
576 			}
577 		}
578 		uint32 checkFP = ComputeFingerprint();
579 		if (fFingerprint != checkFP) {
580 			log_team(LOG_WARNING, "default-catalog(sig=%s, lang=%s) "
581 				"has wrong fingerprint after load (%ld instead of the %ld). "
582 				"The catalog data may be corrupted, so this catalog is skipped.",
583 				fSignature.String(), fLanguageName.String(), checkFP,
584 				fFingerprint);
585 			return B_BAD_DATA;
586 		}
587 	}
588 	return res;
589 }
590 
591 
592 BCatalogAddOn *
593 DefaultCatalog::Instantiate(const entry_ref &catalogOwner, const char *language,
594 	uint32 fingerprint)
595 {
596 	DefaultCatalog *catalog
597 		= new(std::nothrow) DefaultCatalog(catalogOwner, language, fingerprint);
598 	if (catalog && catalog->InitCheck() != B_OK) {
599 		delete catalog;
600 		return NULL;
601 	}
602 	return catalog;
603 }
604 
605 
606 BCatalogAddOn *
607 DefaultCatalog::Create(const char *signature, const char *language)
608 {
609 	DefaultCatalog *catalog
610 		= new(std::nothrow) DefaultCatalog("", signature, language);
611 	if (catalog && catalog->InitCheck() != B_OK) {
612 		delete catalog;
613 		return NULL;
614 	}
615 	return catalog;
616 }
617 
618 extern "C" status_t
619 default_catalog_get_available_languages(BMessage* availableLanguages,
620 	const char* sigPattern, const char* langPattern = NULL,
621 	int32 fingerprint = 0)
622 {
623 	if (availableLanguages == NULL || sigPattern == NULL)
624 		return B_BAD_DATA;
625 
626 	app_info appInfo;
627 	be_app->GetAppInfo(&appInfo);
628 	node_ref nref;
629 	nref.device = appInfo.ref.device;
630 	nref.node = appInfo.ref.directory;
631 	BDirectory appDir(&nref);
632 	BString catalogName("locale/");
633 	catalogName << kCatFolder
634 		<< "/" << sigPattern ;
635 	BPath catalogPath(&appDir, catalogName.String());
636 	BEntry file(catalogPath.Path());
637 	BDirectory dir(&file);
638 
639 	char fileName[B_FILE_NAME_LENGTH];
640 	while(dir.GetNextEntry(&file) == B_OK) {
641 		file.GetName(fileName);
642 		BString langName(fileName);
643 		langName.Replace(kCatExtension, "", 1);
644 		availableLanguages->AddString("language", langName);
645 	}
646 
647 	// search in data folders
648 
649 	directory_which which[] = {
650 		B_USER_DATA_DIRECTORY,
651 		B_COMMON_DATA_DIRECTORY,
652 		B_SYSTEM_DATA_DIRECTORY
653 	};
654 
655 	for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
656 		BPath path;
657 		if (find_directory(which[i], &path) == B_OK) {
658 			catalogName = BString("locale/")
659 				<< kCatFolder
660 				<< "/" << sigPattern;
661 
662 			BPath catalogPath(path.Path(), catalogName.String());
663 			BEntry file(catalogPath.Path());
664 			BDirectory dir(&file);
665 
666 			char fileName[B_FILE_NAME_LENGTH];
667 			while(dir.GetNextEntry(&file) == B_OK) {
668 				file.GetName(fileName);
669 				BString langName(fileName);
670 				langName.Replace(kCatExtension, "", 1);
671 				availableLanguages->AddString("language", langName);
672 			}
673 		}
674 	}
675 
676 	return B_OK;
677 }
678