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