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